Em aplicativos para trabalhar com imagens, a tarefa de redimensionar jipes (imagens compactadas usando o algoritmo JPEG) é bastante comum. Nesse caso, você não pode redimensionar imediatamente e primeiro decodificar os dados originais. Não há nada de novo e complicado nisso, mas se você precisar fazer isso muitos milhões de vezes por dia, é particularmente importante otimizar o desempenho de uma solução desse tipo, que deve ser muito rápida.

Esse problema costuma ser encontrado ao organizar a hospedagem remota para um repositório de imagens, pois a maioria das câmeras e telefones filma no formato JPEG. Todos os dias, os arquivos de fotos dos principais serviços da Web (redes sociais, fóruns, hospedagem de fotos e muitos outros) são reabastecidos com um número significativo dessas imagens; portanto, a questão de como armazenar essas imagens é extremamente importante. Para reduzir o tamanho do tráfego de saída e melhorar o tempo de resposta à solicitação de um usuário, muitos serviços da Web armazenam dezenas de arquivos para uma única imagem em diferentes resoluções. A velocidade de resposta é boa, mas essas cópias ocupam muito espaço. Esse é um grande problema, embora haja outras desvantagens nessa abordagem.
A idéia de resolver esse problema não é armazenar no servidor muitas opções para a imagem original em diferentes resoluções, mas criar dinamicamente a imagem desejada com as dimensões especificadas no original preparado anteriormente e o mais rápido possível. Assim, em tempo real, você pode criar uma imagem da resolução desejada e enviá-la imediatamente ao usuário. É muito importante que a resolução dessa imagem possa ser feita imediatamente para que o dispositivo do usuário não redimensione a tela, pois isso simplesmente não será necessário.
Usar formatos diferentes do JPEG como base para organizar esse repositório de imagens não parece justificado. Obviamente, existem formatos padrão e amplamente usados que oferecem melhor compactação com a mesma qualidade (JPEG2000, WebP), mas a velocidade de codificação e decodificação dessas imagens é muito baixa em comparação com o JPEG; portanto, faz sentido escolher o JPEG como o formato base para armazenar fotos originais, que, se necessário, será dimensionado em tempo real após o recebimento de uma solicitação do usuário.
Obviamente, além dos jipes, cada site costuma ter imagens PNG e GIF, mas geralmente seu número relativo é pequeno e as fotos nesses formatos são extremamente raras. Portanto, esses formatos não terão um impacto significativo na tarefa em questão na maioria dos casos.
Descrição do algoritmo de redimensionamento em tempo real
Portanto, os dados de entrada são arquivos JPEG e, para obter uma decodificação rápida (isso é verdade tanto para a CPU quanto para a GPU), as imagens compactadas devem ter marcadores de reinicialização embutidos. Esses marcadores são descritos no padrão JPEG e parte dos codecs pode trabalhar com eles; os demais sabem como não notá-los. Se os jipes não tiverem esses marcadores, eles poderão ser adicionados antecipadamente usando o utilitário jpegtran. Quando marcadores são adicionados, a imagem não muda, mas o tamanho do arquivo se torna um pouco maior. Como resultado, obtemos o seguinte esquema de trabalho:
- Obter dados de imagem da memória da CPU
- Se houver um perfil de cores, obtenha-o na seção EXIF e salve
- Copie a imagem para a placa de vídeo
- Decodificar JPEG
- Fazemos um redimensionamento de acordo com o algoritmo de Lanczos (diminuição)
- Nitidez
- Codificamos a imagem usando JPEG
- Copiar imagem para o host
- Adicione o perfil de cores original ao arquivo resultante.
Você pode tomar uma decisão mais precisa quando, antes do redimensionamento, a gama inversa é sobreposta em cada componente do pixel, para que o redimensionamento fique no espaço linear e, em seguida, a gama é aplicada novamente, mas após o sharpe. A diferença real para o usuário é pequena, mas existe, e o custo computacional para essa modificação é mínimo. É apenas necessário inserir a superposição da gama inversa e direta no esquema de processamento geral.
Também existe uma solução possível quando a decodificação de jipes é executada em uma CPU multinúcleo usando a biblioteca libjpeg-turbo. Nesse caso, cada imagem é decodificada em um fluxo de CPU separado e todas as outras ações são executadas na placa de vídeo. Com um grande número de núcleos de CPU, isso pode acontecer ainda mais rapidamente, mas haverá uma séria perda de latência. Se a latência ao decodificar um jipe em um único núcleo da CPU for aceitável, essa opção poderá ser muito rápida, especialmente no caso em que os jipes originais tenham uma resolução pequena. À medida que a resolução da imagem original aumenta, o tempo de decodificação do jipe em um fluxo da CPU aumenta, portanto essa opção pode ser adequada apenas para pequenas resoluções.
Requisitos básicos para a tarefa de redimensionamento da web
- É aconselhável não armazenar dezenas de cópias de cada imagem em diferentes resoluções no servidor, mas criar rapidamente a imagem desejada com a resolução correta imediatamente após o recebimento da solicitação. Isso é importante para reduzir o tamanho do armazenamento, caso contrário você terá que armazenar muitas cópias diferentes de cada imagem.
- O problema deve ser resolvido o mais rápido possível. Esta é uma pergunta sobre a qualidade do serviço fornecido em termos de redução do tempo de resposta a uma solicitação do usuário.
- A qualidade da imagem enviada deve ser alta.
- O tamanho do arquivo para a imagem enviada deve ser o menor possível e sua resolução deve corresponder exatamente ao tamanho da janela em que aparece. Os seguintes pontos são importantes aqui:
a) Se o tamanho da imagem não corresponder ao tamanho da janela, o dispositivo do usuário (telefone, tablet, laptop) redimensionará o hardware após a decodificação antes de exibir a imagem na tela. No OpenGL, esse redimensionamento de hardware é feito apenas de acordo com o algoritmo bilinear, que geralmente causa o aparecimento de moiré (manchas) e outros artefatos em imagens contendo pequenos detalhes.
b) O redimensionamento da tela consome adicionalmente energia do dispositivo.
c) Se você usar uma série de imagens pré-dimensionadas para resolver o problema, nem sempre é possível obter o tamanho exato, o que significa que você precisará enviar uma imagem com uma resolução mais alta. O aumento do tamanho da imagem gera mais tráfego, o que eu também gostaria de evitar.
Descrição do esquema geral de trabalho
- Recebemos imagens de usuários em qualquer formato e resolução. Os originais são armazenados em um banco de dados separado (se necessário).
- Off-line, usando o ImageMagick ou software similar, salve o perfil de cores, converta as imagens originais originais no formato BMP ou PPM padrão, redimensione para resolução 1K ou 2K e comprima-as para JPEG e adicione marcadores de reinicialização com o intervalo fixo especificado usando o utilitário jpegtran.
- Compomos um banco de dados com essas imagens de 1K ou 2K.
- Após o recebimento de uma solicitação do usuário, obtemos informações sobre a imagem e o tamanho da janela em que essa imagem deve ser exibida.
- Encontramos a imagem no banco de dados e a enviamos para o redimensionador.
- O redimensionador recebe o arquivo de imagem, decodifica, redimensiona, afia, codifica e insere o perfil de cores original no jipe resultante. Depois disso, ele fornece a imagem para um programa externo.
- Em cada placa de vídeo, você pode executar vários threads e instalar várias placas de vídeo no seu computador - alcançando, assim, a escala de desempenho.
- Tudo isso pode ser feito com base nas placas de vídeo NVIDIA Tesla (por exemplo, P40 ou V100), já que as placas de vídeo NVIDIA GeForce não foram projetadas para operação contínua a longo prazo, e a NVIDIA Quadro possui muitas saídas de vídeo que não são necessárias neste caso. Para resolver esse problema, os requisitos para o tamanho da memória da GPU são mínimos.
- Além disso, no banco de dados com imagens preparadas, você pode alocar dinamicamente um cache para arquivos usados com freqüência. Lá, faz sentido armazenar imagens usadas com frequência de acordo com as estatísticas do período anterior.

Parâmetros do programa
- Largura e altura da nova imagem. Eles podem ser qualquer um e é melhor defini-los explicitamente.
- Modo de afinamento JPEG (subamostragem). Existem três opções: 4: 2: 0, 4: 2: 2 e 4: 4: 4, mas eles geralmente usam 4: 4: 4 ou 4: 2: 0. A qualidade máxima é 4: 4: 4, o tamanho mínimo de quadro é 4: 2: 0. O afinamento é feito para componentes de diferença de cor, que a visão de uma pessoa não percebe, assim como a luminância. Cada modo de dizimação possui seu próprio intervalo ideal para os marcadores de reinicialização atingirem a velocidade máxima de codificação ou decodificação.
- Qualidade de compressão JPEG e modo de dizimação ao criar um banco de dados de imagens.
- O Sharp é feito em uma janela 3x3, o sigma (raio) pode ser controlado.
- Qualidade de compressão JPEG e modo de dizimação ao codificar a imagem final. Normalmente, uma qualidade de pelo menos 90% significa que essa compressão é "visualmente sem perdas", ou seja, usuários não treinados não devem ver artefatos do algoritmo JPEG sob condições de exibição padrão. Acredita-se que, para um usuário treinado, 93 a 95% sejam necessários. Quanto maior esse valor, maior o tamanho do quadro enviado ao usuário e maior o tempo de decodificação e codificação.
Limitações importantes
Reinicie os marcadores. Podemos decodificar rapidamente imagens JPEG em uma placa de vídeo somente se houver marcadores de reinicialização dentro dela. No padrão oficial do JPEG, esses marcadores são descritos, este é um parâmetro padrão. Se não houver marcadores de reinicialização, é impossível paralelizar a decodificação da imagem na placa de vídeo, o que levará a uma velocidade muito baixa do decodificador. Portanto, precisamos de um banco de dados de imagens preparadas nas quais existem esses marcadores.
Algoritmo fixo para codec de imagem. Decodificar e codificar imagens usando o algoritmo JPEG é de longe a opção mais rápida.
A resolução das imagens no banco de dados preparado pode ser qualquer, mas, como opções, consideraremos 1K e 2K (você pode fazer 4K). Você também pode fazer não apenas uma diminuição, mas também um aumento nas imagens ao redimensionar.
Desempenho de redimensionamento rápido
Testamos o aplicativo para redimensionamento rápido do Fastvideo SDK na placa de vídeo NVIDIA Tesla V100 (SO Windows Server 2016, 64 bits, driver 24.21.13.9826) em imagens de 24 bits 1k_wild.ppm e 2k_wild.ppm com resolução de 1K e 2K (1280x720 e 1920 x 1080). Os testes foram realizados para um número diferente de threads em execução na mesma placa de vídeo. Isso requer não mais que 110 MB de memória na placa de vídeo por fluxo. 4 fluxos não precisam mais do que 440 MB.
Primeiro, compactamos a imagem original em JPEG com 90% de qualidade, com redução de 4: 2: 0 ou 4: 4: 4. Em seguida, decodificamos e redimensionamos 2 vezes em largura e altura, fazemos uma nitidez e, em seguida, codificamos novamente com 90% de qualidade em 4: 2: 0 ou 4: 4: 4. Os dados de origem estão na RAM, a imagem final é colocada lá.
O tempo de operação é contado desde o início do carregamento da imagem original da RAM para salvar a imagem processada na RAM. O tempo de inicialização do programa e a alocação de memória na placa de vídeo não estão incluídos nas medições.
Exemplo de linha de comando para uma imagem 1K de 24 bits
PhotoHostingSample.exe -i 1k_wild.90.444.jpg -o 1k_wild.640.jpg -outputWidth 640 -q 90 -s 444 -sharp_after 0.95 -repetição 200
Referência para processar uma imagem 1K em uma thread
Decodificação (incluindo transferência de dados para a placa de vídeo): 0,70 ms
Redimensionar duas vezes (em largura e altura): 0,27 ms
Nítido: 0,02 ms
Codificação JPEG (incluindo transferência de dados da placa de vídeo): 0,20 ms
Tempo total por quadro: 1,2 msDesempenho para 1K
| Qualidade | Diluição | Redimensionar | Streams | Taxa de quadros (Hz) |
1 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 1 | 868/682 |
2 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 2 | 1039/790 |
3 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 3 | 993/831 |
4 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 4 | 1003/740 |
Desempenho para 2K
| Qualidade | Diluição | Redimensionar | Streams | Taxa de quadros (Hz) |
1 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 1 | 732/643 |
2 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 2 | 913/762 |
3 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 3 | 891/742 |
4 | 90% | 4: 4: 4/4: 2: 0 | 2 vezes | 4 | 923/763 |
A diluição de 4: 2: 0 para a imagem de origem reduz a velocidade, mas o tamanho dos arquivos de origem e de destino fica menor. Ao alternar para 4: 2: 0, o grau de paralelismo diminui 4 vezes, já que agora o bloco 16x16 é considerado como uma única unidade, portanto, nesse modo, a velocidade é menor que para 4: 4: 4.
O desempenho é determinado principalmente pelo estágio de decodificação JPEG, porque nesse estágio a imagem possui a resolução máxima e a complexidade computacional desse estágio de processamento é superior a todas as outras.
Sumário
Os resultados do teste mostraram que, para a placa de vídeo NVIDIA Tesla V100, a velocidade de processamento de imagens de 1K e 2K é máxima quando 2-4 fluxos são lançados ao mesmo tempo e varia de 800 a 1000 quadros por segundo por placa de vídeo. O processamento de imagens 1K é mais rápido que 2K e o trabalho com imagens 4: 2: 0 é sempre mais lento que com 4: 4: 4. Para obter o resultado final do desempenho, você precisa determinar com precisão todos os parâmetros do programa e otimizá-lo para um modelo específico da placa de vídeo.
A latência da ordem de um milissegundo é um bom resultado. Até onde sabemos, essa latência não pode ser obtida para uma tarefa de redimensionamento semelhante na CPU (mesmo que não seja necessário codificar e decodificar jipes), então esse é outro argumento importante a favor do uso de placas de vídeo em soluções de processamento de imagem de alto desempenho.
Podem ser necessárias até 16 placas gráficas NVIDIA Tesla V100 para processar um bilhão de jipes por dia com resoluções de 1K ou 2K. Alguns de nossos clientes já usam essa solução, enquanto outros a testam em suas tarefas.
Redimensionar jipes em uma placa de vídeo pode ser muito útil, não apenas para serviços da web. Há um grande número de aplicativos de processamento de imagem de alto desempenho em que essa funcionalidade pode estar em demanda. Por exemplo, muitas vezes é necessário um redimensionamento rápido para quase qualquer esquema de processamento de imagens recebidas das câmeras antes de exibir uma imagem em um monitor. Esta solução pode funcionar para Windows / Linux em qualquer placa de vídeo NVIDIA: Tegra K1 / X1 / X2 / Xavier, GeForce GT / GTX / RTX, Quadro, Tesla.
Vantagens de uma solução de redimensionamento rápido em uma placa gráfica
- Redução significativa no tamanho do armazenamento para imagens de origem
- Reduzindo os custos primários dos custos de infraestrutura (hardware e software)
- Melhorando a qualidade do serviço devido ao curto tempo de resposta
- Redução de tráfego de saída
- Menor consumo de energia nos dispositivos do usuário
- Confiabilidade e velocidade da solução apresentada, que já foi testada em grandes conjuntos de dados
- Tempo de desenvolvimento reduzido para comercializar esses aplicativos para Linux e Windows
- Escalabilidade de uma solução que pode funcionar em uma única placa de vídeo e como parte de um cluster
- Rápido retorno do investimento para esses projetos
Quem pode estar interessado
A biblioteca para redimensionamento rápido de jipes pode ser usada em serviços da Web altamente carregados, grandes lojas online, redes sociais, sistemas de gerenciamento de fotos on-line, comércio eletrônico, em quase todos os grandes softwares de gerenciamento corporativo.
Os desenvolvedores de software podem usar essa biblioteca, que fornece latência da ordem de vários milissegundos para redimensionar jipes com uma resolução de 1K, 2K e 4K em uma placa de vídeo.
Aparentemente, essa abordagem pode ser mais rápida que a solução NVIDIA DALI para decodificação rápida de jipes, redimensionamento e preparação de imagens no estágio de treinamento de redes neurais para o Deep Learning.
O que mais pode ser feito
- Além de redimensionar e afiar, você pode adicionar recorte ao algoritmo existente, girar 90/180/270, aplicar uma marca d'água, controlar o brilho e o contraste.
- Otimização da solução para placas de vídeo NVIDIA Tesla P40 e V100.
- Desempenho de otimização adicional decodificador JPEG.
- Modo Burst para decodificar jipes em uma placa de vídeo.