Depois de concluir a criação da arquitetura da web para o nosso novo quadrinho na web
Meow the Infinite , decidi que era hora de escrever alguns artigos técnicos há muito esperados. Este artigo se concentrará em um filtro que desenvolvi há vários anos. Nunca foi discutido no campo da compressão de vídeo, embora me pareça que vale a pena fazer.
Em 2011, desenvolvi o “filtro half-pel”. Esse é um tipo especial de filtro que obtém uma imagem recebida e mostra de maneira mais convincente a aparência da imagem quando deslocada
exatamente meio pixel .
Você provavelmente está se perguntando por que esse filtro pode ser necessário. De fato, eles são bastante comuns em codecs de vídeo modernos. Os codecs de vídeo usam filtros semelhantes para pegar fragmentos de quadros anteriores e usá-los em quadros subsequentes. Os codecs mais antigos moviam os dados do quadro apenas um pixel inteiro de cada vez, mas os novos codecs foram além e permitiram um deslocamento de meio ou até um quarto de pixel para transmitir melhor pequenos movimentos.
Ao analisar o comportamento dos algoritmos de compensação de movimento nos filtros tradicionais de meio-pél,
Jeff Roberts descobriu que, quando aplicados repetidamente em quadros seqüenciais, eles se degradam rapidamente, forçando outras partes do compressor de vídeo a usar mais dados do que o necessário para corrigir artefatos. Se você desabilitar essas correções e observar os resultados "brutos" do filtro halfpel, esta é a imagem original:
vira isso:
apenas um segundo depois o vídeo. Como deveria, é deslocado para o lado, porque cada quadro mudou a imagem em meio pixel. Mas o resultado não parece uma versão deslocada da imagem original, está seriamente distorcido.
Durante o filtro "um segundo vídeo", na verdade, é aplicado várias vezes - 60 se o vídeo for reproduzido com uma frequência de 60 quadros por segundo. Mas, idealmente, precisamos de filtros resistentes a essas distorções. Se os tivéssemos, os vídeos com rolagem suave não teriam sido codificados com tantas correções de artefatos, o que os tornaria menos, ou melhores, ou ambos.
Se você conhece o campo da compactação de vídeo, pode se perguntar por que precisamos usar o filtro halfpel mais de uma vez. No final, se aplicarmos o filtro halfpel duas vezes, já moveremos um pixel inteiro. Então, por que não usar os dados de
dois quadros atrás e levá-
los ?
A resposta não é tão simples. Primeiro, quanto mais dados precisarmos codificar, menor será a compactação. Portanto, se começarmos a codificar sem a necessidade de muitos dados, como "a partir de qual quadro coletar dados", o vídeo não será compactado muito bem.
Mas isso não é o mais importante. O principal problema é que, se precisarmos obter informações de quadros anteriores, teremos
que armazená-los . Para preservar os dois quadros anteriores, em vez de um, você precisa adivinhar que possui duas vezes mais memória. Para CPUs modernas, esse não é um problema especial, eles têm muita memória e essa ninharia não os incomoda. Mas isso é um
problema para você, se você deseja criar um formato de vídeo rápido, portátil e amplamente usado, que funcione em dispositivos com pouca memória (telefones celulares, eletrônicos embutidos etc.).
Realmente não queremos armazenar vários quadros para compensar o movimento, apenas para não usar um filtro de meio fotorreceptor. Portanto, fui instruído a descobrir o que exatamente está acontecendo aqui e a descobrir se posso criar um filtro que não tenha esses problemas.
Antes disso, eu nunca havia trabalhado com filtros e não tinha ideia de como eles geralmente são desenvolvidos. Curiosamente, acabou sendo a meu favor, porque eu tive que olhar para esse problema sem preconceitos.
O básico
Eu rapidamente percebi que os filtros halfpel mais populares têm uma estrutura semelhante: para cada pixel na imagem de saída, são capturados 2 a 8 pixels da imagem de entrada, que são amostrados e misturados com certos coeficientes. Os filtros diferentes diferem apenas no número de pixels de origem amostrados (geralmente no jargão dos desenvolvedores de filtros, chamados de torneira) e nos fatores de mistura de pixels. Esses coeficientes são frequentemente chamados de "núcleo do filtro" e é tudo o que é necessário para descrever completamente o filtro.
Se você estiver familiarizado com qualquer tipo de amostragem ou reamostragem de imagens (por exemplo, dimensionamento de imagens), isso deve ficar claro para você. Essencialmente, os filtros fazem a mesma coisa. Como a compactação de vídeo é uma vasta área na qual vários estudos são realizados, é óbvio que existem muitas
outras maneiras de compensar o movimento, além da simples filtragem. Porém, codecs comuns geralmente usam procedimentos de compensação de movimento com filtros de meio-pél, que são essencialmente idênticos aos filtros de escala de imagem: eles apenas pegam os pixels originais, multiplicam-nos por alguns pesos, adicionam-nos e obtêm os pixels de saída.
A necessidade de "nitidez"
Então, precisamos mudar a imagem em meio pixel. Se você é um programador gráfico, mas não está familiarizado com a filtragem, pode pensar: "Eu também tenho um problema, basta usar um filtro bilinear". Este é um processo padrão no trabalho com gráficos, quando precisamos calcular valores intermediários entre dois elementos de dados recebidos, como acontece aqui.
Um filtro bilinear para mover exatamente meio pixel pode ser facilmente descrito pelo seguinte núcleo de filtro:
Isso vai funcionar, mas não sem problemas. Se o seu objetivo é imagens de alta qualidade e, no caso de compactação de vídeo, o objetivo é exatamente isso, então um filtro bilinear não é a melhor solução, porque adiciona mais desfoque ao resultado do que o necessário. Não é
tanto , mas
mais do que outros filtros criam.
Para mostrar isso claramente, eis uma imagem aproximada do olho da morsa a partir da imagem original após uma única aplicação dos filtros mais comuns:
À esquerda está o original, à direita há filtragem bilinear. Entre eles estão os filtros halfpel mais usados de codecs de vídeo. Se você observar atentamente, poderá ver que quase todas as imagens são semelhantes,
exceto uma bilinear, que é um pouco mais desfocada. Embora não haja muito desfoque, se o seu objetivo principal é a qualidade da imagem, isso é suficiente para preferir um filtro diferente a um filtro bilinear.
Então, como outros filtros “mantêm” a nitidez e evitam a desfocagem? Vamos lembrar como é o núcleo do desfoque bilinear:
BilinearKernel[] = {1.0/2.0, 1.0/2.0};
É muito simples Para mudar a imagem em meio pixel, pegamos um pixel e o misturamos 50% com o vizinho. Isso é tudo. Pode-se imaginar como isso “desfoca” a imagem, porque nos locais em que o pixel branco brilhante é adjacente ao preto escuro, esses dois pixels são calculados em média durante a filtragem bilinear, criando um pixel cinza que “suaviza” a borda. Isso acontece com todos os pixels, portanto, literalmente, todas as áreas em que há uma clara diferença de cor ou brilho. suavizado.
É por isso que, nos codecs de alta qualidade, a filtragem bilinear não é usada para compensação de movimento (embora possa ser usada em outros casos). Em vez disso, são usados filtros que preservam a nitidez, por exemplo, como:
Como você pode ver, onde a filtragem bilinear levou em consideração apenas dois pixels, esses filtros levaram em consideração seis (h.264) ou até oito (HEVC). Além disso, eles não apenas calculam os valores médios ponderados usuais desses pixels, mas usam pesos
negativos para alguns pixels para
subtrair esses pixels de outros valores.
Por que eles estão fazendo isso?
Na verdade, não é difícil entender isso: usando valores positivos e negativos e também considerando uma "janela" mais ampla, o filtro pode levar em consideração a
diferença entre os pixels adjacentes e simular a nitidez dos dois pixels mais próximos em relação aos vizinhos mais distantes. Isso permite manter a nitidez do resultado da imagem nos locais em que os pixels diferem significativamente de seus vizinhos, enquanto a média ainda é usada para criar valores confiáveis de trocas de "meio pixel", que necessariamente refletem a combinação de pixels da imagem recebida.
Filtragem instável
Então, o problema foi resolvido? Sim, é possível, mas se você precisar fazer apenas um deslocamento de meio pixel. No entanto, esses filtros de "nitidez" (e eu uso esse termo aqui intencionalmente) realmente fazem algo perigoso,
essencialmente semelhante ao que a filtragem bilinear faz. Eles apenas sabem como esconder isso.
Onde a filtragem bilinear
reduz a nitidez da imagem, esses filtros padrão
aumentam , como a operação de nitidez em alguns programas gráficos. A quantidade de nitidez é muito pequena; portanto, se executarmos o filtro apenas uma vez, não perceberemos isso. Mas se a filtragem for realizada várias vezes, isso poderá se tornar muito perceptível.
E, infelizmente, como essa nitidez é processual e depende da diferença entre os pixels,
cria um loop de feedback que continuará a afiar a mesma borda repetidamente até destruir a imagem. Você pode mostrar isso com exemplos específicos.
Acima - a imagem original, abaixo - com filtragem bilinear, executou mais de 60 quadros:
Como seria de esperar, o desfoque simplesmente continua a reduzir a nitidez da imagem até que ela fique bastante embaçada. Agora, o original estará na parte superior e o filtro de halfpel do codec h.264 que será executado por 60 quadros na parte inferior:
Está vendo todo esse lixo? O filtro fez o mesmo que o efeito de "desfoque" da filtragem bilinear, mas
vice-versa - "aumentou a nitidez da imagem", de modo que todas as partes em que os detalhes foram transformados em padrões claros / escuros fortemente distorcidos.
O codec HEVC usando 8 pixels se comporta melhor? Bem, definitivamente faz melhor que h.264:
mas se aumentarmos o tempo de 60 quadros (1 segundo) para 120 quadros (2 segundos), ainda veremos que há feedback e a imagem é destruída:
Para quem gosta de processamento de sinal, adicionarei um filtro com janela sinc (chamado filtro Lanczos) para referência:
Não explicarei neste artigo por que alguém pode estar interessado em "janela sinc", mas basta dizer que esse filtro é popular por razões teóricas; portanto, veja como fica ao processar 60 quadros (1 segundo):
e ao processar 120 quadros (2 segundos):
Melhor que o h.264 e quase o mesmo que o HEVC.
Filtragem estável
Como podemos obter melhores resultados do que h.264, HEVC e window window sinc? E quanto melhor eles podem ser?
Eu
esperava ver perguntas semelhantes na literatura sobre compactação de vídeo e elas deveriam ser bem conhecidas dos especialistas em compactação, mas, de fato (pelo menos para 2011), não encontrei ninguém que ao menos afirmasse que isso era um problema. Então eu tive que encontrar uma solução sozinha.
Felizmente, a declaração do problema é muito simples: crie um filtro que possa ser aplicado tantas vezes quanto possível, para que a imagem pareça a mesma do início.
Eu chamo essa definição de "filtragem estável" porque, na minha opinião, ela pode ser considerada uma propriedade de filtro. Um filtro é "estável" se não cair em seu loop de feedback, ou seja, pode ser aplicado repetidamente sem criar artefatos. Um filtro é "instável" se ele cria artefatos que são amplificados pelo uso repetido e eventualmente destrói a imagem.
Repito, não entendo por que esse tópico não é considerado na literatura sobre codecs de vídeo ou processamento de imagens. Talvez ele use uma terminologia diferente, mas eu não a conheci. O conceito de "feedback" está bem estabelecido no campo do trabalho com som. mas não é uma questão importante no processamento de imagens. Talvez porque geralmente os filtros devam ser aplicados apenas uma vez?
Se eu fosse um especialista nesse campo, provavelmente teria uma opinião sobre esse assunto, e talvez eu conhecesse aqueles recantos de literatura especializada onde já existem soluções para esse problema, conhecidas por poucos. Mas, como eu disse no começo do artigo, nunca tinha conseguido criar filtros antes, então procurei apenas em artigos conhecidos (embora valha a pena notar que há pelo menos uma pessoa conhecida na literatura que também não ouviu nada parecido com isso )
Então, pela manhã, eles me disseram que precisávamos desse filtro e durante todo o dia tentei criá-lo. Minha abordagem foi simples: criei um programa que executou o filtro centenas de vezes e, no final, produzi uma imagem para que eu pudesse ver o resultado de execuções longas. Depois experimentei diferentes coeficientes de filtro e observei os resultados. Era literalmente um processo direcional de tentativa e erro.
Cerca de uma hora depois, peguei os melhores coeficientes de filtro adequados para esta tarefa (mas eles tinham uma falha, que discutirei na segunda parte do artigo):
MyKernel[] = {1.0/32.0, -4.0/32.0, 19.0/32.0, 19.0/32.0, -4.0/32.0, 1.0/32.0};
Este núcleo está à beira de afiar e desfocar. Como a nitidez sempre leva a um feedback que cria artefatos vívidos e óbvios, esse núcleo do filtro prefere um pouco de desfoque, de modo que a imagem pareça um pouco mais "opaca".
É assim que ele cuida de 60 quadros. Para referência, mostrei todos os filtros nesta ordem: a imagem original (sem filtragem), meu filtro, bilinear, Lanczos, h.264, HEVC:
Como você pode ver, meu filtro fornece resultados um pouco mais embaçados do que os filtros de nitidez, mas não possui artefatos de nitidez inaceitáveis após 60 quadros. No entanto, você pode preferir artefatos de desfoque a objetos de nitidez, para escolher entre o melhor filtro de nitidez (Lanczos) e o meu. No entanto, se aumentarmos o número para 120 quadros, meu filtro estará fora de competição:
Após 300 quadros, todos os filtros, exceto o meu, tornam-se uma piada de mau gosto:
Depois de 600 quadros, a piada se torna ainda mais cruel:
Você nem precisa dizer o que acontece depois de 900 quadros:
Quão estável é isso?
Nesse estágio, ele naturalmente se perguntará: meu filtro é
realmente estável ou é apenas um borrão muito lento, muito mais lento que a filtragem bilinear? Talvez depois de milhares de repetições, meu filtro desfoque
gradualmente a imagem?
Surpreendentemente, a resposta parece ser negativa. Embora um pouco de desfoque seja adicionado ao longo de cerca de cem das primeiras sobreposições, parece que o filtro
converge para uma representação estável da imagem, que
nunca é degradada. Aqui está outra imagem ampliada de um olho de morsa:
Da esquerda para a direita: a imagem original, meu filtro foi aplicado 60 vezes, 120 vezes, 300 vezes, 600 e 900 vezes. Como você pode ver, o desfoque converge para um estado estável, que não é mais degradado, mesmo depois de centenas de sobreposições de filtro. Por outro lado, compare isso com a sincronização em janelas para o mesmo número de amostras (toque) e veja o quão ruim (e rápido!) Os artefatos formam o feedback e criam um resultado inútil:
Meu filtro parece muito estável e, comparado a todos os filtros que vi, cria os melhores resultados após o uso repetido. Parece que possui uma certa propriedade "assintótica", na qual os dados convergem rapidamente para uma imagem suavizada (limitada) e, em seguida, essa imagem suavizada é salva e não executa degradação ilimitada para concluir o lixo.
Eu até tentei aplicar o filtro um
milhão de vezes, e parece que, após as primeiras centenas de sobreposições, ele não se deteriora mais. Sem uma análise matemática melhor (e ainda não encontrei uma solução matemática que possa prová-la exatamente, mas sei com certeza que está em algum lugar), não posso dizer com certeza que em algum lugar após bilhões ou trilhões de sobreposições que -não vai quebrar. Em testes razoáveis, não consegui detectar mais degradação.
É o melhor filtro Halfpel estável para seis torneiras?
Nesse estágio, seria lógico fazer a pergunta: isso é realmente o melhor que pode ser encontrado? A intuição nos diz que não, porque eu não tinha absolutamente nenhum conhecimento sobre o desenvolvimento de filtros e quase não olhei na literatura, peguei esse filtro em apenas uma hora. Pelo menos, pode-se
supor que, depois de um estudo tão breve, eu não encontraria um filtro definitivo com a melhor conquista de todos.
Essa suposição é verdadeira? E se for verdade,
qual será o melhor filtro final? Vou discutir isso em mais detalhes na segunda parte do artigo.