Anotação de Imagem como Metadado EXIF

Longo demais; não vou ler: Este artigo questiona práticas correntes na anotação de imagens para visão de computador e propõe armazenar anotações como metadados da imagem, dispensando o arquivo de texto pareado.

Uma das milhares de Imagens do ‘Caprichoso’, nosso dataset de gado zebuino. Breve no GitHub – Imagem: Cownt CC BY-NC-SA 4.0 Deed

Não sei se a prática teve origem na tentativa de contornar limitações tecnológicas dos primórdios. Trabalhando com visão de computador sempre achei um tanto desconcertante que seja preciso criar um arquivo de texto [sidecar file] – contendo as coordenadas das regiões de interesse e das caixas delimitadoras [bounding boxes] – para trabalhar em ‘tandem’ com o arquivo da imagem, quando o próprio arquivo de imagem possui um ambiente ideal, até mesmo Turing-completo [como o JPEG XL], para armazenar esses dados de uma forma estruturada. Isso parece muito ineficiente.

A proposta aqui é tentar simplificar o sistema de arquivos do dataset, eliminando os arquivos de texto secundários, e verificar se há algum ganho importante que justifique mudanças no atual paradigma do processo de treinamento, pelo menos para pequenos conjuntos de dados e tarefas de ajuste fino.

Os arquivos secundários, por definição, armazenam dados (geralmente metadados) que não são suportados pelo formato de um arquivo de origem. Obviamente, isso não é verdade com os arquivos de imagem digital modernos.

Procuro também entender os problemas técnicos e conceituais – e porque não dizer, éticos – relacionados à inserção/escrita/leitura de dados nessas estruturas/ambientes, bem como verificar se há algo a ganhar no processo de treinamento, pelo menos para pequenos datasets, datasets proprietários e/ou tarefas de ajuste fino.

Criar uma tag EXIF personalizada

De acordo com a Wikipedia [Inglês] “O padrão XMP foi projetado para ser extensível, permitindo aos usuários adicionar seus próprios tipos personalizados de metadados”.

Em um mundo perfeito, essa tag personalizada teria seu próprio tipo de dados. Para este exercício usaremos a vocação natural que as tags XMP têm para lidar com strings.

Uma imagem digital, além da informação ótica contida nos pixels, têm um amplo setor dedicado a armazenar informações gerais sobre outros detalhes. Esse setor é dividido em um grande número de campos de metadados, chamados ‘tags’, que armazenam informações específicas sobre o arquivo, como velocidade do obturador, exposição, condições ambientais, localização do dispositivo e inúmeros outros bits. Uma tabela, em outras palavras.

Há um grande número de tags XMP [link em inglês por falta de um em português – lusófonos, precisamos despertar para a tecnologia]. Os diversos dispositivos disponíveis no mercado adotam diversos formatos, sendo EXIF um entre eles.

Tags definidas pelo usuário

Precisamos então criar uma tag para conter nosso rótulo; nossa própria tag EXIF.

No mundo perfeito deste exercício, uma tag ‘Label’ para imagens estaria incluída na especificação XMP e populá-la seria responsabilidade das ferramentas de anotação.

As anotações são parte integrante da aprendizagem de máquina supervisionada. Em uma sessão de trabalho, a ferramenta de anotação normalmente cria, no momento em que um anotador seleciona a região da imagem que contém o item a ser rotulado, um arquivo de texto contendo as coordenadas da anotação da imagem, estruturado em um determinado formato – json, xml, CSV, etc.

São essas coordenadas que permitem ao sistema de AI sobrepor as “caixas delimitadoras” [‘bounding boxes’] – aqueles quadrados já familiares que delimitam os itens-alvo para detecção, como na imagem que ilustra este post. Esse arquivo de texto vive em um casamento indissolúvel com o arquivo de imagem e, para fins de visão de computador computacional, são sempre referenciados juntos.

Um argumento comum é que este esquema de separação imagem/texto permite maior flexibilidade nas anotações, atomização do dataset, etc. Mas contraponho o argumento de que nada é muito diferente quando todos são metadados.

Em uma etiqueta adequadadamente formatada, os dados de texto permanecem compartimentados e manipulá-los não será mais difícil do que manipular um arquivo de texto. Ainda é perfeitamente possível manter o conteúdo das tags sincronizado com arquivos de texto mantidos fora do dataset. O dataset não precisa mais de um sistema de arquivos [FileSystem]. Além disso, “grandes datasets de arquivos pareados têm custos consideráveis, bem como preocupações com a baixa qualidade” (Jia et al., 2021)[0].

Simplificar o dataset

Vamos então nos livrar do arquivo de texto e armazenar nossas anotações como uma tag EXIF do arquivo de imagem. Existem muitos módulos disponíveis em Python para esta tarefa, mas pouca diversidade. Muitos estão desatualizados. Uma pesquisa nos canais Anaconda (conda, conda-forge) e PyPi (pip) retorna módulos como pyexiv2; piexif e PyExifTool. Este último é um ‘wrapper’ Python para o ExifTool, que é uma aplicação escrita em Pearl. É meu preferido no momento. Não detalharei aqui as peculiaridades de cada um.

Com exiftool é possível executar manipulações avançadas em tags. Vamos usá-lo para criar uma nova tag chamada ‘Label’:

O processo envolve editar o arquivo exif.config contendo as tags que queremos definir, conforme estipulado na documentação do módulo:

%Image::ExifTool::UserDefined = (
    # Todas as tags EXIF tags são adicionadas à tabela principal ‘Main table’
    'Image::ExifTool::Exif::Main' => {
        # Example 1.  EXIF:NewEXIFTag
        0xd000 => {
            Name => 'Label',
            Writable => 'int16u',
            WriteGroup => 'IFD0',
        },
        # definir mais tags abaixo...
    }

O espaço de tags EXIF é domínio dos fabricantes de hardware. Programas comuns de edição de imagens como Gimp, Photoshop e outros oferecem maneiras de acessar e editar tags EXIF em seus ambientes.

Intervenções programáticas via Python, C++ requerem abordagens mais técnicas e usuários experientes.

Alternativamente, para uma versão mais simples do experimento, podemos pular a construção de tags personalizadas e usar – após renomeá-las adequadamente – uma ou duas das tags predefinidas na especificação EXIF e disponíveis na maioria dos dispositivos. Exemplos dessas são as tags UserComments, MakerNotes, etc.

Neste experimento, a anotação do rótulo da imagem será serializada para uma tag personalizada chamada ‘Label’ [1] na tabela XMP/EXIF da imagem – assumindo que a tag tenha sido criada ou renomeada no exif.config

def writeToEXIFtag (dadosAnotados)
     #pseudocódigo por enquanto
     Imagem.Exif.Label = dadosAnotados

em vez do arquivo de texto emparelhado [json, xml, csv, etc.]

 def writeToJSONFile(path, fileName, data):
     fileName = fileName.split(".")[0]
     filePathNameWExt = path + '/' + fileName + '.json'
     with open(filePathNameWExt, 'w') as fp:
         json.dump(data,fp)

como no processo usual.

Neste projeto, para maior praticidade [integração com outros módulos, etc.], o melhor caminho parece ser utilizar ambientes virtuais, como virtualenv e conda. Dificilmente é possível reunir exatamente os mesmos pacotes em ambas as plataformas. No momento estou utilizando ambientes que configurei com módulos que montei através da prática não muito limpa de misturar conda+pip. Ainda tenho coisas para descobrir – não tenho muita experiência com Pearl e estou tendo dificuldade em fazer com que tudo [exiftool + pyexiftool] funcione junto.

A favor

  • Processamento mais eficiente [a verificar].
  • Os arquivos de imagem do conjunto de dados podem ser renomeados e usados em qualquer outro dataset sem trabalho adicional.
  • Sem problemas com formatos diferentes. Esses Xlabels [rótulos EXIF] podem coexistir com os arquivos de anotação pareados.
  • A simplicidade traz ganhos pedagógicos; uma curva de aprendizagem [humana] menos acentuada.
  • Câmeras podem pré-anotar imagens automaticamente – pelo menos categorias universais, como COCO [isso é um ‘Pró’?].

Contra

  • Esse esquema reduz em muito a flexibilidade dos dados [a verificar]
  • Aumento do tamanho do conjunto de dados [a verificar]
  • Menos controle sobre conjuntos de dados e anotações [a verificar]
  • Os problemas habituais da economia de vigilância [câmeras detectando, identificando, classificando…]
  • <Insira seu Contra aqui>

Epílogo

Não tenho conhecimento de ideias semelhantes e gostaria de saber se existem. Eu também gostaria de saber se na verdade estou chegando atrasado a uma solução já rejeitada. Ainda estou nos estágios iniciais e receber feedback é parte fundamental do processo.

Estarei relatando os progressos [ou falta de]. Tenho o esqueleto do repositório no GitHub[2], e vou estar lapidando e finalizando a versão inicial nos próximos dias. É um projeto modesto – praticamente todo o README está neste post, porque a ideia é muito simples, como eu creio que todos podem ver.

[0] Scaling Up Visual and Vision-Language Representation Learning With Noisy Text Supervision https://arxiv.org/pdf/2102.05918.pdf

[1] A questão de se criar novas tags, ou renomear alguma existente, [ex. UserComments → Label], ou ambos, ou ainda outra opção com outro tipo de dados, está aberta, assim como a questão de se usar tags simples ou combinadas – por exemplo, para atomizar as coordenadas das caixas delimitadoras e outras informações

[2] https://github.com/VoxLeone/XLabel


APÊNDICE

Para criar uma tag personalizada usando o pyexiftool, você precisa ter a biblioteca pyexiftool instalada em seu ambiente Python. Como mencionado no artigo, pyexiftool é um ‘wrapper’ Python para exiftool, que é uma aplicação escrita nativamente em Pearl. Aqui está um exemplo de função que cria uma tag personalizada em uma imagem usando o pyexiftool:

import exiftool

def create_custom_tag(image_path, tag_name, tag_value):
    with exiftool.ExifTool() as et:
        et.execute(f'-{tag_name}={tag_value}', image_path)

# Exemplo de uso:
image_path = "caminho/para/imagem.jpg"
tag_name = "XMP:CustomTag"
tag_value = "Valor da tag personalizada"

create_custom_tag(image_path, tag_name, tag_value)

Substitua ‘caminho/para/imagem.jpg‘ pelo caminho real para a imagem em que você deseja criar a tag. Defina tag_name como o nome desejado para sua tag e tag_value como o valor que você deseja atribuir a ela.

Esta função vai então usar pyexiftool para executar a ferramenta exiftool em seu sistema e definir a tag personalizada na imagem especificada.

É preciso ter o exiftool instalado em seu sistema para que esta biblioteca funcione corretamente.

A Visão do Computador, Nós e os Presidenciáveis

Neste post vou abordar um assunto com o qual os leitores podem facilmente se identificar, pois o que vamos discutir está firmemente arraigado em nossa vida diária. Faça um esforço para ler até o fim. Você pode se surpreender e até gostar.

Arte: Vox Leone

Como já informei, aqui em nosso lab temos usado o incrível Jupyter Notebook, nas tarefas rotineiras de análise de dados, e também em pesquisas relacionadas à visão de computador e processamento de linguagem natural. Isso nos permite colaborar (trabalhar em rede), consolidar dados que serão usados em outras fases do trabalho e gerar as mais diversas visualizações, que são necessárias nas relações com clientes e o público geral.

Nas últimas semanas estive absorvido na tarefa de desenvolver nossa própria aplicação de reconhecimento facial, usando tecnologias populares na área, como Tensorflow, Computer Vision 2, Matplotlib e Pandas. Tudo orquestrado pela excelente biblioteca DeepFace, que disponibiliza oito modelos pré-treinados.

Os resultados que tivemos foram impressionantes. Animado com os experimentos, preparei este Notebook para demonstrar aos leitores como somos vistos pelos olhos digitais que pululam ao nosso redor (sempre em detrimento de nossa privacidade) e quais as tecnologias empregadas.

Os aeroportos resumem a experiência. Todos sabem que ao entrar nesse tipo de ambiente nós somos enquadrados de todos os ângulos imagináveis, por sensores cada vez mais sofisticados, entre os quais se destacam os onipresentes sensores óticos. Já discutimos neste blog – e vamos continuar – algumas questões éticas e legais a respeito do assunto, mas hoje quero demonstrar sucintamente como a análise facial é feita materialmente e quais as informações que os sistemas de vigilância podem obter bastando apenas voltar seu olhar em nossa direção.

A Tecnologia

Vou ter que usar um pouco de código, e algum jargão, mas você pode pular até o resultado da análise da imagem e discussão, mais abaixo.

Seguindo a tendência de grande parte da indústria, nossa aplicação – escrita em Python – é desenvolvida e executada dentro de um ambiente virtual Conda. O núcleo do ambiente é a versátil biblioteca DeepFace, desenvolvida pela Alphabet (Google), aqui instalada através do repositório pip. (*)ressalto que não é recomendado misturar os canais dos repositórios Conda e pip em um mesmo ambiente, mas neste experimento se torna necessário usar pip pelo fato de DeepFace ainda não estar disponível no repositório Conda.

Depois de ter instalado a DeepFace e suas dependências no ambiente virtual, iniciamos o script importando a DeepFace e outras bibliotecas das quais ela depende, como TensorFlow, CV2 e Matplotilb. TensorFlow é o motor da aplicação. CV2 é responsável pela visão de computador, neste caso digitalizando apropriadamente as imagens. Matplotlib é a responsável pela renderização das imagens (plotagem). Vou comentar o código com as explicações.

Análise dos resultados

Et voilà! Depois de alguns segundos temos o resultado da análise. O algoritmo, mesmo nesta configuração básica, é sensível o bastante para detectar sete variações emocionais, além da idade aparente e do gênero. Ele também é capaz de diferenciar seis etnicidades, um tanto arbitrárias, devo dizer, espelhando a classificação dos países anglo-saxões.

Eu anoto neste ponto que usamos a biblioteca DeepFace em sua configuração original, assim como o modelo de aprendizado de máquina pré-treinado em bancos de imagens com milhões de amostras. Não tivemos ainda tempo hábil para introduzir grandes modificações no código e treinar nossos próprios modelos, o que já está sendo feito – e será mostrado oportunamente.

Esta é a análise que os sistemas fazem sobre mim [sobre nós] no momento em que saio do táxi na porta do aeroporto.

É uma análise aparentemente trivial de uma imagem trivial. Contexto real da foto: acabei de chegar a uma chácara para uma festa de aniversário – alguns anos atrás.

A Análise

O algoritmo me interpreta como um homem branco de aparentes 37 anos – sendo agradavelmente generoso na avaliação etária. É interessante que ele me atribui uma certa negritude, como alternativa, muito mais do que seria esperado de minha ascendência mediterrânea, que fica relegada a um baixíssimo patamar de probabilidade. Minha expressão é calma, que o algoritmo traduz como neutra. Como indicam os números na tabela em inglês, posso estar também um pouco triste, e com um certo nojinho.

Contudo, o resultado me atribui uma significativa aparência de medo, como o segundo resultado mais provável. Por que eu estaria com medo nesta foto? Acabei de chegar a uma festa e a tarde de sábado parece promissora – no aeroporto isso acenderia uma luz amarela no centro de comando da segurança. Interrompo o teste e começo a tentar entender esse resultado aparentemente divergente.

Depois de algumas horas eu tive um insight que realmente me encantou. Eu entendi que, de fato, o algoritmo havia conseguido ver através da minha máscara social. Ele captou uma verdade íntima e bastante sutil: eu realmente sou ansioso em situações sociais, e sempre demoro um pouco a me adaptar aos ambientes – valentemente, com um sorriso no rosto. Mas a ansiedade – uma forma de medo – deve sulcar nossa face de uma maneira óbvia a sistemas de precisão, que conhecem dezenas de milhões de rostos que respondem sempre da mesma maneira; da mesma maneira humana demais. Minúsculos espasmos musculares, resultantes de nossas emoções, não importa quão sutis, deixam sua marca em nossa face, e em imagens de alta resolução nos desnudam de qualquer abrigo.

Ver o sistema em ação elevou em vários graus minha confiança na tecnologia [além de uma óbiva inquietação]. Depois de muitas rodadas do sistema, com imagens diferentes, eu tenho razões para crer que esse tipo de algoritmo é consistente na detecção de sinais e marcadores de emoções bastante sutis.

Revelando emoções ocultas

Existe uma atividade humana em que ser capaz de detectar as emoções alheias seria de muita utilidade: a política, por exemplo. Seria do maior interesse público tentar sondar os reais sentimentos daqueles que se apresentam para gerir a coisa pública. Nessa atividade em que a manipulação das emoções é tão corriqueira, seria interessante tentar ver através das máscaras.

Acontece que em abril de 2022 nós temos essas fantásticas ferramentas e estamos aprendendo a dominá-las. Vamos tentar, então, fazer a mesma análise que fiz acima, de mim mesmo, em imagens casuais dos principais políticos que se apresentam para a disputa presidencial deste ano. Vamos usar fotos tiradas da internet e submetê-las ao mesmo algoritmo.

Com certeza os candidatos não vão se importar, pois todos eles, com palavras ou atos, já se manifestaram apoiadores da vigilância eletrônica na vida do cidadão. É uma ironia deliciosa poder usar contra os representantes do sistema a mesma tecnologia que o sistema usa para nos oprimir delicadamente. Vai ser, no mínimo, divertido.

Nota: as imagens abaixo foram buscadas no site da Wikimedia Commons. Foram escolhidas por serem adequadas a este projeto. São apresentadas na ordem alfabética dos sobrenomes dos retratados.

Este Notebook é melhor visualizado em um desktop.

Bolsonaro, Jair

O perfil de Bolsonaro revela uma grande carga de emotividade. Uma mistura de raiva, medo e tristeza, indicando um rosto que quer esconder o que sente. O modelo também é generoso na atribuição da idade, desvio que foi notado em todas as imagens analisadas [creio que sei a razão para isso, que comento na conclusão do artigo].

Dória, João

Vamos concordar que a imagem não precisa de análise por AI. Embora o modelo opte pela etnicidade Latina, na verdade ele se mostra indeciso. Isso implica em um certo viés colorista. Comento na conclusão.

Gomes, Ciro

Uma expressão neutra com um forte sinal – isolado – de tristeza. Minha avaliação humana concorda.

Lula da Silva, Luiz Inácio

Tristeza generalizada, com um sinal de nojo/desconforto [talvez pela situação]. A idade também é grandemente descontada.

Moro, Sérgio

A raiva e a tristeza predominam. O desvio etário se mostra menor. Um autêntico Latino Hispânico.

Tebet, Simone

Embora não esteja exibindo um sorriso exuberante [como Dória], o modelo atribuiu à imagem um sinal de alegria muito sólido. Não há emoções conflitantes, indicando sinceridade e abertura. A discrepância etária também é menor aqui. Surpreendentemente o modelo não atribui um peso muito forte à herança árabe da senadora.

Conclusão e notas

Como eu disse anteriormente, o modelo Facenet, um dos oito implementados na biblioteca DeepFace, se comporta de forma muito satisfatória. É preciso lembrar que o modelo reflete a amostra com a qual foi treinado. A medida que mais iterações do processo de treinamento venham a incorporar mais e mais segmentos populacionais, a precisão deverá aumentar.

Deixo uma crítica à classificação étnica empregada pelo modelo. Ela reflete a abordagem do censo americano, mas claramente não é mais adequada. Uma revisão se impõe. Os desvios notados com relação à idade das pessoas analisadas, se devem, provavelmente, à baixa resolução das imagens, que nivela os valores dos pixels dando margem a um enganoso efeito de filtro. A discrepância parece ser diretamente proporcional à idade do analisado [quanto mais idoso, maior a discrepância]. Esse desvio é responsável também pelo ‘colorismo’ do software, que dá grande peso à palidez aparente da imagem na classificação étnica.

Vamos continuar experimentando e trabalhando para tornar o modelo mais fiel às nossas características.

Referências

Jupyter Notebook – https://jupyter.org/

Anaconda – https://anaconda.org/

DeepFace – https://github.com/serengil/deepface

TensorFlow – https://www.tensorflow.org/