OpenCV é uma biblioteca com uma história de desenvolvimento contínuo em 20 anos. A idade em que você começa a se aprofundar, procurando um destino. Existem projetos baseados nisso que tornaram a vida de alguém melhor, alguém mais feliz? Você pode fazer isso sozinho? Em busca de respostas e um desejo de descobrir módulos OpenCV anteriormente desconhecidos, quero criar aplicativos que “funcionem lindamente” - para que, a princípio, exista “uau” e só então você diga “ah, sim, é visão computacional”.
O direito do primeiro artigo foi um experimento com a transferência de estilos de artistas mundiais em fotografia. No artigo, você aprenderá qual é o coração do procedimento e sobre o relativamente novo OpenCV.js - a versão JavaScript da biblioteca OpenCV.

Transferência de estilo
Os oponentes do aprendizado de máquina vão me perdoar, mas o principal componente do artigo de hoje será uma profunda rede convolucional. Porque funciona. Não há como treinar redes neurais no OpenCV, mas você pode executar modelos existentes. Usaremos a rede CycleGAN pré- treinada . Os autores, pelos quais são muito gratos, oferecem redes totalmente gratuitas para baixar imagens que convertem imagens de maçãs em laranjas, cavalos em zebras, imagens de satélite em mapas, fotos de inverno em fotos de verão e muito mais. Além disso, o procedimento de treinamento em rede permite que você tenha dois modelos de gerador trabalhando nas duas direções ao mesmo tempo. Ou seja, ensinando a transformação do inverno no verão, você obterá um modelo para pintar paisagens de inverno em fotografias de verão. Uma oferta única que é impossível recusar.
No nosso exemplo, tomamos modelos que transformam fotos em pinturas de artistas. Ou seja, Vincent Van Gogh, Claude Monet, Paul Cezanne ou em todo o gênero de gravuras japonesas Ukiyo-e. Ou seja, teremos quatro redes separadas à nossa disposição. Vale ressaltar que, para o treinamento de cada um, não foi usada uma foto do artista, mas toda uma multidão; assim, os autores tentaram treinar a rede neural para não mudar o estilo de uma obra, mas, por assim dizer, adotar o estilo de escrita.
Opencv.js
O OpenCV é uma biblioteca desenvolvida em C ++, enquanto na maior parte de sua funcionalidade existe a possibilidade de criar wrappers automáticos que chamam métodos nativos. Oficialmente, os wrappers para as linguagens Python e Java são suportados. Além disso, existem soluções personalizadas para Go , PHP . Se você tem experiência no uso de outros idiomas, seria ótimo saber em qual e graças a quais esforços.
O OpenCV.js é um projeto que ganhou direito à vida graças ao programa Google Summer of Code em 2017. A propósito, uma vez que o próprio módulo de aprendizado profundo OpenCV foi criado e melhorado significativamente em sua estrutura. Diferentemente de outras linguagens, o OpenCV.js no momento não é um invólucro de métodos nativos em JavaScript, mas uma compilação completa usando o Emscripten usando o LLVM e o Clang. Ele permite que você crie um arquivo a partir do aplicativo ou da biblioteca C e C ++ .js
que pode ser executado, por exemplo, em um navegador.
Por exemplo,
#include <iostream> int main(int argc, char** argv) { std::cout << "Hello, world!" << std::endl; return 0; }
Compilando no asm.js
emcc main.cpp -s WASM=0 -o main.js
E carregar:
<!DOCTYPE html> <html> <head> <script src="main.js" type="text/javascript"></script> </head> </html>

Você pode conectar o OpenCV.js ao seu projeto da seguinte maneira (criação noturna):
<script src="https://docs.opencv.org/master/opencv.js" type="text/javascript"></script>
Uma biblioteca adicional para leitura de imagens, trabalhando com a câmera e outras coisas, escrita manualmente em JavaScript, também pode ser útil:
<script src="https://docs.opencv.org/master/utils.js" type="text/javascript"></script>
Carregar Imagens
As imagens no OpenCV.js podem ser lidas a partir de elementos como canvas
ou img
. Isso significa que o download direto de arquivos de imagem para eles continua sendo uma tarefa do usuário. Por conveniência, a função auxiliar addFileInputHandler
carrega automaticamente a imagem no elemento de canvas
desejado quando uma imagem é selecionada no disco com o clique de um botão.
var utils = new Utils(''); utils.addFileInputHandler('fileInput', 'canvasInput'); var img = cv.imread('canvasInput');
onde
<input type="file" id="fileInput" name="file" accept="image/*" /> <canvas id="canvasInput" ></canvas>
O ponto importante é que img
será uma imagem RGBA de 4 canais, que difere do cv::imread
usual, que cria uma imagem BGR. Isso deve ser levado em consideração, por exemplo, ao transportar algoritmos de outros idiomas.
Com a renderização, tudo é simples - basta chamar imshow
com o id
canvas
desejada (espera RGB ou RGBA).
cv.imshow("canvasOutput", img);
Algoritmo
Todo o algoritmo de processamento de imagem é o lançamento de uma rede neural. Suponha que o que acontece dentro permaneça mágico, precisamos apenas preparar a entrada correta e interpretar a previsão corretamente (saída da rede).
A rede considerada neste exemplo recebe um tensor quadridimensional com valores de float
no intervalo [-1, 1]
. Cada uma das dimensões, em ordem de velocidade de mudança, é o índice da imagem, canais, altura e largura. Esse estilo é chamado NCHW, e o próprio tensor é chamado de blob, objeto grande binário. A tarefa de pré-processamento é converter uma imagem OpenCV, cujas intensidades são intercaladas, tem um intervalo de valores [0, 255]
tipo unsigned char
em um blob NCHW com um intervalo de valores [-1, 1]
.

um pedaço do Kremlin de Nizhny Novgorod (como uma pessoa vê)

visualização intercalada (como o OpenCV armazena)

visão plana (o que a rede precisa)
Como pós-processamento, será necessário executar as transformações inversas: a rede retorna um blob NCHW com valores no intervalo [-1, 1]
, que devem ser reembalados na imagem, normalizados para [0, 255]
e convertidos em unsigned char
.
Assim, levando em consideração todos os recursos de leitura e gravação de imagens do OpenCV.js, as seguintes etapas estão tomando forma:
imread -> RGBA -> BGR [0, 255] -> NCHW [-1, 1] -> [] [] -> NCHW [-1, 1] -> RGB [0, 255] -> imshow
Olhando para o pipeline resultante, surgem perguntas, por que a rede não pode funcionar imediatamente em RGBA intercalado e retornar RGB intercalado? Por que são necessárias transformações extras para permutação e normalização de pixels? A resposta é que uma rede neural é um objeto matemático que realiza transformações nos dados de entrada de uma determinada distribuição. No nosso caso, ela foi treinada para receber dados neste formulário, portanto, para obter os resultados desejados, é necessário reproduzir o pré-processamento que os autores utilizaram no treinamento.
Implementação
A rede neural que executaremos é armazenada na forma de um arquivo binário, que deve primeiro ser carregado no sistema de arquivos local.
var net; var url = 'style_vangogh.t7'; utils.createFileFromUrl('style_vangogh.t7', url, () => { net = cv.readNet('style_vangogh.t7'); });
A propósito, url
é um link completo para o arquivo. Nesse caso, apenas carregamos o arquivo ao lado da página HTML atual, mas você pode substituí-lo pela fonte original (nesse caso, o tempo de download pode ser maior).
Lendo uma imagem da canvas
e convertendo de RGBA para BGR:
var imgRGBA = cv.imread('canvasInput'); var imgBGR = new cv.Mat(imgRGBA.rows, imgRGBA.cols, cv.CV_8UC3); cv.cvtColor(imgRGBA, imgBGR, cv.COLOR_RGBA2BGR);
Criando um blob 4D em que a função blobFromImage
converte em um float
dados float
usando constantes de normalização. Então - inicie a rede.
var blob = cv.blobFromImage(imgBGR, 1.0 / 127.5,
O resultado é convertido novamente na imagem do tipo desejado e no intervalo de valores [0, 255]
No momento, o OpenCV.js está sendo construído no modo semi-automático. No sentido em que nem todos os módulos e métodos deles recebem as assinaturas correspondentes em JavaScript. Por exemplo, para um módulo dnn, a lista de funções válidas é definida da seguinte maneira:
dnn = {'dnn_Net': ['setInput', 'forward'], '': ['readNetFromCaffe', 'readNetFromTensorflow', 'readNetFromTorch', 'readNetFromDarknet', 'readNetFromONNX', 'readNet', 'blobFromImage']}
A última conversão, dividindo o blob em três canais e depois misturando-os em uma imagem, pode ser realizada com a imagesFromBlob
método imagesFromBlob
, que simplesmente ainda não foi adicionado à lista acima. Talvez essa seja sua primeira contribuição ao desenvolvimento do OpenCV? ;)
Conclusão
Como demonstração, preparei uma página no GitHub onde você pode testar o código resultante: https://dkurtaev.imtqy.com/opencv4arts (Cuidado! Ao fazer o download de uma rede de cerca de 22 MB, economize seu tráfego. Também é recomendável recarregar a página para cada nova imagem, caso contrário, a qualidade processamento subsequente é de alguma forma fortemente distorcido). Esteja preparado para um longo processo de processamento ou tente redimensionar a imagem, que será o resultado, um controle deslizante.
Enquanto trabalhava no artigo e escolhia a própria imagem que se tornaria o rosto dela, acidentalmente encontrei uma foto do meu amigo, que retrata o Kremlin de nossa cidade e tudo se encaixou - surgiu com o nome do artigo e só então senti que deveria ser assim. Sugiro que você experimente o aplicativo nas fotos do seu lugar favorito e, talvez, conte algo interessante sobre isso nos comentários ou em uma carta pessoal.
De mim - um fato divertido. A maioria dos residentes de Nizhny Novgorod e da região de Nizhny Novgorod usa a palavra “sair” no sentido da palavra “encaixar-se” (encontre um local gratuito). Por exemplo, a pergunta "Vamos limpar seu carro?" significa "Temos espaço suficiente no seu carro?", mas não "Podemos limpar o seu carro?". Quando estudantes de outras áreas nos procuram para estágios de verão, gostamos de contar esse fato - muitos ficam sinceramente surpresos.
Links úteis