scieee AI-readable full text Open interactive document viewer

Inteligência Artificial - Visão Computacional (Aplicações Práticas com OpenCV e Yolo)

Fagner, Raimundo; SOUZA, WENNDISSON DA; Gadelha, Vitor Fernando de Souza; Lopes Martiniano, Alexandre; RODRIGUES E SILVA, NIVALDO

Abstract

Vivemos em uma era onde as máquinas não apenas processam dados, mas também aprendem a enxergar o mundo ao nosso redor. A Visão Computacional, um dos pilares da inteligência artificial, representa essa capacidade das máquinas de interpretar imagens e vídeos com precisão semelhante , e em alguns casos, superior à percepção humana. Esta tecnologia é o motor por trás de inovações transformadoras, como carros autônomos, reconhecimento facial, diagnósticos médicos por imagem e sistemas de vigilância inteligente. Este livro foi desenvolvido para conduzir o leitor por uma jornada acessível e envolvente no universo da Visão Computacional, desde os fundamentos da análise de imagens até a construção de aplicações capazes de extrair informações visuais com significado. Com uma abordagem didática e progressiva, buscamos tornar o conhecimento técnico acessível tanto para estudantes iniciantes quanto para profissionais que desejam aplicar ou aprofundar seus conhecimentos na área. No primeiro módulo, apresentamos os conceitos básicos da Visão Computacional, como a formação das imagens digitais, modelos de cor, operações de pré-processamento e extração de características. O segundo módulo explora algoritmos clássicos e técnicas fundamentais, incluindo detecção de bordas, segmentação, transformações geométricas e análise de movimento. No terceiro módulo, entramos no campo da Visão Computacional moderna para detecção e classificação de objetos, que revolucionaram a forma como sistemas visuais são treinados e utilizados. O módulo final apresenta estudos de caso aplicados, como a detecção de objetos em tempo real, classificações de imagens da agricultura , para classificação de frutas e saúde de plantas. Mais do que um manual técnico, este livro busca despertar a curiosidade e o entusiasmo do leitor para explorar as múltiplas possibilidades da visão artificial. Ao dominar essas ferramentas, o leitor estará apto a criar soluções inovadoras, capazes de dar às máquinas a capacidade de ver, entender e interagir com o mundo de maneira inteligente. Que esta leitura seja o ponto de partida para grandes descobertas e conquistas no fascinante campo da Visão Computacional.

Full text

25-300451.0 CDD-001.535 Dados Internacionais de Catalogação na Publicação (CIP) (Câmara Brasileira do Livro, SP, Brasil) Inteligência artificial [livro eletrônico] : visão computacional : (aplicações práticas com OpenCV e Yolo) / Raimundo Fagner Costa...[et al.]. -- Manaus, AM : Ed. dos Autores, 2025. PDF Outros autores: Wenndisson da Silva Souza, Vitor Fernando de Souza Gadelha, Alexandre Lopes Martiniano, Nivaldo Rodrigues e Silva. ISBN 978-65-01-68384-3 1. Ciência da computação 2. Inteligência artificial 3. Visão por computador I. Costa, Raimundo Fagner. II. Souza, Wenndisson da Silva. III. Gadelha, Vitor Fernando de Souza. IV. Martiniano, Alexandre Lopes. V. Silva, Nivaldo Rodrigues e. Índices para catálogo sistemático: 1. Visão computacional 001.535 Eliete Marques da Silva - Bibliotecária - CRB-8/9380 DOI: 10.5281/zenodo.17127815 Expediente do IFAM MINISTÉRIO DA EDUCAÇÃO SECRETARIA DE EDUCAÇÃO PROFISSIONAL E TECNOLÓGICA INSTITUTO FEDERAL DE EDUCAÇÃO, CIÊNCIA E TECNOLOGIA DO AMAZONAS Reitor Jaime Cavalcante Alves Pró-Reitor de Administração Fábio Teixeira Lima Pró-Reitor de Gestão de Pessoas Leandro Amorim Damasceno Pró-Reitora de Ensino Rosângela Santos da Silva Pró-Reitora de Extensão Maria Francisca Morais de Lima Pró-Reitor de Pesquisa, Pós-Graduação e Inovação Paulo Henrique Rocha Aride Diretor Geral do Campus Manaus Distrito Industrial Nivaldo Rodrigues e Silva Expediente do Projeto CITHA MINISTÉRIO DA EDUCAÇÃO SECRETARIA DE EDUCAÇÃO PROFISSIONAL E TECNOLÓGICA INSTITUTO FEDERAL DE EDUCAÇÃO, CIÊNCIA E TECNOLOGIA DO AMAZONAS Gestores Nivaldo Rodrigues e Silva Samirames da Silva Fleury Alyson de Jesus dos Santos Maria Cassiana Andrade Braga Adanilton Rabelo de Andrade Coordenadores Tiago Francisco Andrade Diocesano Jaidson Brandão da Costa Elcivan dos Santos Silva Martinho Correia Barros Adelino Maia Galvão Filho Expediente de Produção Autor(es) Raimundo Fagner Costa Wenndisson da Silva Souza Vitor Fernando de Souza Gadelha Alexandre Lopes Martiniano Nivaldo Rodrigues e Silva Avaliação Pedagógica Samirames da Silva Fleury Diagramadores Raimundo Fagner Costa Wenndisson da Silva Souza Fabio Serra Ribeiro Couto Revisores de Texto Alexandre Lopes Martiniano Inteligência Artificial - Visão Computacional (Aplicações Práticas) Autores Raimundo Fagner Costa. Wenndisson da Silva Souza Vitor Fernando de Souza Gadelha Alexandre Lopes Martiniano Nivaldo Rodrigues e Silva Prefácio por Nivaldo Rodrigues e Silva Revisão Alexandre Lopes Martiniano 1ª Edição Manaus - AM 2025 Lista de Expressões para Enriquecimento de Conteúdo Este material foi cuidadosamente estruturado para apoiar sua jornada de aprendizado. Ao longo dos capítulos, você encontrará diversas chamadas sinalizadas por ícones especiais, que ajudarão a destacar pontos-chave e enriquecer sua compreensão. Durante a diagramação, esses ícones serão inseridos conforme as indicações dos autores, guiando você para diferentes tipos de conteúdo e atividades que potencializam seu estudo. Fique Alerta! Destaque para conceitos, expressões e trechos fundamentais que merecem sua atenção especial para a compreensão do conteúdo. Iniciando o diálogo... Espaço para reflexão crítica. Aqui você será convidado(a) a problematizar os temas abordados, relacionando-os com sua experiência e buscando conexões relevantes para aprofundar seu aprendizado. Conhecendo um pouco mais! Indicação de fontes complementares, como livros, entrevistas, vídeos, aplicativos, links e outros recursos para ampliar seu conhecimento sobre o tema. Caso Prático Aplicação direta do conteúdo em exemplos concretos, para facilitar a fixação e demonstrar a utilidade do que foi aprendido. Copie e Teste! Trechos de código prontos para serem copiados e executados, para que você possa experimentar, validar e explorar na prática os conceitos estudados. Prefácio Vivemos em uma era onde as máquinas não apenas processam dados, mas também aprendem a enxergar o mundo ao nosso redor. A Visão Computacional, um dos pilares da inteligência artificial, representa essa capacidade das máquinas de interpretar imagens e vídeos com precisão semelhante , e em alguns casos, superior à percepção humana. Esta tecnologia é o motor por trás de inovações transformadoras, como carros autônomos, reconhecimento facial, diagnósticos médicos por imagem e sistemas de vigilância inteligente. Este livro foi desenvolvido para conduzir o leitor por uma jornada acessível e envolvente no universo da Visão Computacional, desde os fundamentos da análise de imagens até a construção de aplicações capazes de extrair informações visuais com significado. Com uma abordagem didática e progressiva, buscamos tornar o conhecimento técnico acessível tanto para estudantes iniciantes quanto para profissionais que desejam aplicar ou aprofundar seus conhecimentos na área. No primeiro módulo, apresentamos os conceitos básicos da Visão Computacional, como a formação das imagens digitais, modelos de cor, operações de pré-processamento e extração de características. O segundo módulo explora algoritmos clássicos e técnicas fundamentais, incluindo detecção de bordas, segmentação, transformações geométricas e análise de movimento. No terceiro módulo, entramos no campo da Visão Computacional moderna para detecção e classificação de objetos, que revolucionaram a forma como sistemas visuais são treinados e utilizados. O módulo final apresenta estudos de caso aplicados, como a detecção de objetos em tempo real, classificações de imagens da agricultura , para classificação de frutas e saúde de plantas. Mais do que um manual técnico, este livro busca despertar a curiosidade e o entusiasmo do leitor para explorar as múltiplas possibilidades da visão artificial. Ao dominar essas ferramentas, o leitor estará apto a criar soluções inovadoras, capazes de dar às máquinas a capacidade de ver, entender e interagir com o mundo de maneira inteligente. Que esta leitura seja o ponto de partida para grandes descobertas e conquistas no fascinante campo da Visão Computacional. Projeto de Capacitação e Interiorização em Tecnologias Habilitadoras na Amazônia - CITHA O projeto CITHA surge com o objetivo de fortalecer a economia da Amazônia por meio do incentivo ao empreendedorismo local e do desenvolvimento sustentável. Sua proposta é capacitar profissionais e impulsionar a criação de startups voltadas para a bioeconomia, além de apoiar cooperativas locais na melhoria de seus processos produtivos. A implementação de tecnologias inovadoras é uma das estratégias centrais do projeto, visando oferecer soluções eficientes que atendam às necessidades regionais, como a otimização dos recursos naturais e a melhoria da infraestrutura local. Ao longo de sua execução, o projeto se compromete a integrar os diversos stakeholders, como governos, empresas, ONGs e comunidades, por meio da capacitação da mão de obra local. O objetivo é formar um capital intelectual qualificado, capaz de apoiar uma governança eficiente, promover a inovação e assegurar a sustentabilidade. O CITHA dedica-se à criação de processos internos que incentivem o desenvolvimento de novos métodos e tecnologias, adaptáveis às particularidades do território amazônico. Em síntese, o projeto CITHA visa criar um ciclo de desenvolvimento que não só incentive o empreendedorismo, mas também promova a modernização das estruturas locais, elevando a qualidade de vida das populações da Amazônia. Focado em áreas como bioeconomia, inovação e transferência de tecnologia, o projeto busca estabelecer um ecossistema mais forte e autossustentável, capaz de responder eficientemente às demandas do mercado e da sociedade. CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL •Extração de Características: Fase responsável por identificar e representar informações relevantes de uma imagem, transformando dados visuais em descrições numéricas ou simbólicas que facilitam a análise computacional. A extração de características visa capturar padrões distintivos, como formas, texturas, cores ou estruturas, que permitam diferenciar objetos ou cenas. Por exemplo, em uma imagem de um rosto, as características extraídas podem incluir a distância entre os olhos, a textura da pele ou a orientação de bordas. Esses atributos são usados em tarefas como reconhecimento facial, classificação de objetos e correspondência de imagens. Figura 1.8: Exemplo de extração de características. Fonte: Elaborado pelos autores. •Reconhecimento de Padrões: Fase responsável por classificar ou agrupar as imagens de acordo com o seu conjunto de características. O reconhecimento de padrões é a etapa em que o sistema identifica e classifica elementos na imagem com base em características extraídas, comparando-as com padrões aprendidos. Por exemplo, ao analisar uma foto, o algoritmo pode reconhecer um padrão de rosto ou um padrão de placa de carro. Isso é feito usando técnicas como aprendizado de máquina e redes neurais. Figura 1.9: Exemplo de reconhecimento de padrões. Fonte: Adaptado de [2]. Nos próximos módulos, iremos explorar com mais detalhes todas essas etapas, descrevendo e aplicando os conceitos na prática, para a partir de imagens, realizar o seu pré15 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL processamento, selecionar regiões de interesse, extrair características e classificar imagens para resolução de problemas ou tomada de decisão. Conhecendo um pouco mais! Para aprofundar seus conhecimentos, recomendamos a leitura do livro Deep Learning, de Ian Goodfellow, Yoshua Bengio e Aaron Courville, além dos recursos disponíveis na biblioteca do IFAM e cursos especializados em plataformas como Coursera e edX. 1.5 Preparando o Ambiente Após uma breve introdução à área de Visão Computacional, vamos configurar o nosso ambiente de estudo. A etapa de preparação do ambiente é fundamental para o desenvolvimento de aplicações de Visão Computacional. Inicialmente, é necessário instalar o Python, preferencialmente em versões estáveis e atualizadas (como a 3.8 ou superior), para garantir compatibilidade com as ferramentas utilizadas. 1.6 Instalação do Python no Windows Esta seção foi desenvolvida visando a instalação do Python no sistema operacional Windows. Para realizar a instalação do Python, basta acessar o site oficial do Python na seção de downloads em https://www.python.org/downloads e fazer o download do mesmo conforme apresentado na imagem. Conhecendo um pouco mais! Caso queira instalar no Linux, acesse o link https://python.org.br/instalacao-linux/. Figura 1.10: Site do Python – Área de Download. Fonte: Elaborado pelos autores. 16 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL •Passo 1: Execute o arquivo baixado e marque as opções destacadas na imagem. Figura 1.11: Instalação do Python - Passo 1. Fonte: Elaborado pelos autores. •Passo 2: Após realizar a instalação, a tela seguinte será exibida. Nela, clique no botão ”Close”. Figura 1.12: Instalação do Python - Passo 2. Fonte: Elaborado pelos autores. 1.6.1 Testando o Python Após instalar o Python, podemos testar se ele está funcionando, ou seja, se foi instalado corretamente. Para isso, vá no Menu Iniciar do Windows e digite CMD e abra o Prompt de Comando. Figura 1.13: Testando o Python - Passo 3. Fonte: Elaborado pelos autores. 17 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL No Prompt de Comando, ou terminal, digite o comando python, conforme ilustra a imagem abaixo. Se aparecer uma mensagem semelhante à apresentada na tela a seguir, é porque deu tudo certo. Figura 1.14: Instalação do Python - Passo 4. Fonte: Elaborado pelos autores. 1.6.2 Bibliotecas Necessárias Para enriquecer a linguagem Python com ferramentas que otimizem o desenvolvimento de sistemas de Visão Computacional, é essencial a instalação de algumas bibliotecas. Estas bibliotecas são pacotes que contêm funções que não fazem parte do conjunto nativo da linguagem. A lista a seguir apresenta as bibliotecas selecionadas para o desenvolvimento dos exemplos práticos que serão apresentados neste material. •OpenCV: Principal biblioteca para processamento de imagens e vídeos, usada para captura, manipulação e análise visual. •NumPy: Pacote essencial para operações matemáticas em arrays multidimensionais, base para manipulação de dados em Visão Computacional. •Matplotlib: Ferramenta para visualização de dados e gráficos, útil para representar resultados de análises de imagens. •Statistics: Biblioteca para cálculos estatísticos, aplicada na análise quantitativa de dados extraídos de imagens. •Scikit-Learn: Pacote de aprendizado de máquina que oferece algoritmos para classificação e reconhecimento de padrões em imagens. Após listar as bibliotecas que usaremos neste ebook, a seguir, os conceitos e a instalação de cada uma delas serão apresentados. 1.6.3 OpenCV OpenCV (Open Source Computer Vision Library) é uma biblioteca open source amplamente utilizada para processamento de imagens e visão computacional. Desenvolvida em C++, mas com suporte a Python, Java e outras linguagens, ela oferece mais de 2.500 algoritmos otimizados para: 18 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL • Detecção de objetos (rostos, veículos, textos). • Reconhecimento de padrões. • Realidade aumentada. • Processamento de vídeo em tempo real. A biblioteca OpenCV tem um conjunto de funções prontas para manipular e processar imagens digitais. Para instalá-la em seu computador, execute no terminal o comando: pip install opencv-python 1.6.4 NumPy A biblioteca NumPy (Numerical Python) é essencial para computação científica em Python, oferecendo estruturas de dados otimizadas para trabalhar com arranjos multidimensionais (vetores e matrizes), cálculos numéricos e operações matriciais. Como já mencionado anteriormente, imagens digitais nada mais são do que matrizes com valores numéricos, e a biblioteca NumPy irá nos auxiliar em diversas operações. Para instalá-la em seu computador, execute no terminal o comando: pip install numpy 1.6.5 Matplotlib A biblioteca Matplotlib possui um conjunto de ferramentas que nos permite gerar diferentes tipos de gráficos, como, por exemplo, histogramas, além de permitir visualizar imagens também. Para instalá-la em seu computador, execute no terminal o comando: pip install matplotlib 1.6.6 Statistics A biblioteca statistics (https://docs.python.org/3/library/statistics.html) possui um conjunto de ferramentas que facilitam cálculos estatísticos, como a obtenção da média, moda e mediana de uma lista de valores. A biblioteca statistics é nativa da linguagem Python e não se faz necessária sua instalação. A biblioteca statistics oferece diversas vantagens para análises estatísticas rápidas e eficientes. Suas principais vantagens incluem: •Praticidade, pois fornece funções prontas para cálculos comuns (média, mediana, moda, desvio padrão e variância), eliminando a necessidade de implementar manualmente essas operações. •Precisão, com suporte a diferentes tipos numéricos (int, float e Decimal), garantindo resultados confiáveis. •Simplicidade, sendo leve e integrada nativamente ao Python, sem exigir instalação adicional. •Facilidade de uso, com uma sintaxe intuitiva que acelera o desenvolvimento de análises estatísticas básicas. 19 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL 1.6.7 Scikit-Learn A biblioteca Scikit-Learn possui um conjunto de ferramentas que torna simples a implementação de algoritmos para aprendizado de máquina. Esta biblioteca será utilizada mais à frente em tarefas como reconhecimento de padrões e classificação de objetos. Para instalá-la em seu computador, execute no terminal o comando: pip install scikit-learn 1.7 Aquisição de Imagens A aquisição de imagens representa a etapa inicial no fluxo de processamento digital de imagens. Nessa fase, o mundo físico, formado por objetos, luz e cores, é convertido em uma representação digital compreensível por sistemas computacionais. Tal conversão é viabilizada por dispositivos de captura, como câmeras digitais e scanners, que transformam a luz refletida pelos objetos em sinais elétricos, os quais são posteriormente digitalizados e armazenados como imagens digitais. 1.7.1 Dispositivos de Aquisição de Imagens Entre os dispositivos utilizados para a aquisição de imagens, destacam-se: •Câmeras digitais: amplamente utilizadas em aplicações diversas, desde fotografia amadora até sistemas industriais e científicos. Elas utilizam sensores optoeletrônicos (como CCD ou CMOS) para transformar a luz em sinais elétricos. •Scanners: empregados principalmente na digitalização de documentos e imagens físicas, utilizando mecanismos de varredura ponto a ponto. •Dispositivos especializados: como câmeras térmicas, tomógrafos, satélites e drones, utilizados em contextos específicos como segurança, medicina e sensoriamento remoto. 1.7.2 Câmeras Digitais As câmeras digitais são dispositivos responsáveis pela conversão da luz refletida por objetos em sinais digitais que podem ser armazenados e processados. Ao contrário das câmeras analógicas, que registram a luz em filmes fotossensíveis, as câmeras digitais utilizam sensores eletrônicos para capturar a luz incidente e transformá-la em sinais elétricos. O funcionamento básico de uma câmera ocorre da seguinte forma: a luz se propaga em linha reta até atingir uma superfície qualquer, podendo ser um móvel, um carro, um animal ou até mesmo você. A câmera contém em seu interior uma câmara escura em forma de uma caixa, com um pequeno orifício no meio por onde os raios de luz passam. E é aí que a mágica que produz de tudo, de selfies a imagens de guerra, acontece. Neste pequeno orifício presente na câmara, é posicionada uma lente convergente, permitindo que a luz proveniente do objeto ou cena que se deseja capturar atravesse e incida em seu interior, projetando uma imagem invertida na face interna, contrária ao orifício. Para arquivar a imagem projetada, as câmeras analógicas (muito utilizadas antes da popularização das câmeras digitais) empregavam em seu interior filmes químicos sensíveis à luz, conforme o apresentado na imagem abaixo. 20 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL Figura 1.15: Exemplo de funcionamento de uma câmera. Fonte: Wikimedia Commons Nas câmeras digitais utilizadas hoje em dia, os sensores eletrônicos sensíveis à luz desempenham essa tarefa. Eles atuam convertendo a imagem obtida em dados digitais. Os dois tipos de sensores eletrônicos mais usados para esse procedimento são os CCD e os CMOS, que serão apresentados a seguir. 1.7.3 Sensores CCD e CMOS Estes sensores são os principais componentes presentes nas câmeras digitais. Os sensores CCD (Charge-Coupled Device) eCMOS (Complementary Metal-Oxide Semiconductor) são os componentes que capturam a luz e a transformam em sinais elétricos, gerando imagens digitais. Eles são responsáveis por registrar as cenas e convertê-las em dados binários (bits e bytes), que depois são processados e armazenados como arquivos digitais. Ambos os sensores estão presentes em câmeras fotográficas, smartphones, webcams e outros dispositivos de imagem. Os sensores CCD são chips que transformam luz em imagens digitais. Quando a luz atinge o sensor, cada pixel captura os fótons (partículas de luz) e os converte em cargas elétricas. Essas cargas são transferidas, uma após a outra, até a borda do sensor, onde um circuito as transforma em sinais digitais (bits e bytes). Esse processo garante imagens de alta qualidade, com cores precisas e pouco ruído, sendo ideal para aplicações profissionais como telescópios e câmeras científicas. Por outro lado, os sensores CMOS também convertem luz em imagens digitais, mas com uma abordagem diferente. Em vez de transferir as cargas elétricas pixel por pixel como no CCD, cada pixel em um CMOS possui seu próprio circuito de leitura, permitindo que os sinais sejam processados individualmente e em paralelo. Isso torna os sensores CMOS mais rápidos e energeticamente eficientes, além de mais baratos para produção em massa. Embora tradicionalmente apresentassem mais ruído em condições de pouca luz, os CMOS modernos evoluíram significativamente em qualidade de imagem, equiparando-se em muitos aspectos aos CCD. 21 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL 1.7.4 Formação de Imagens Ao compreender o funcionamento dos sensores CCD e CMOS, é possível definir uma imagem digital como uma matriz, ou até mesmo como uma tabela, bidimensional com valores numéricos que representam a luminosidade refletida por uma determinada cena ou objeto. Esses sensores realizam a conversão da luz incidente em sinais elétricos, os quais são posteriormente digitalizados. Quando esses valores são organizados espacialmente em linhas e colunas e associados a componentes de cor, tornam-se capazes de formar uma representação visual coerente com a realidade observada, permitindo ao observador identificar paisagens, objetos, pessoas, entre outros elementos presentes na cena. Fique Alerta! Isso mesmo, uma imagem digital na realidade é uma matriz, ou até mesmo uma tabela, onde cada célula, representada por um número, ao final, vai ser traduzida para uma cor. Observe a imagem abaixo, onde foi aplicado um zoom até você enxergar a olho nu as células, que no contexto das imagens digitais serão chamadas de pixels. Figura 1.16: Exemplo de estrutura de uma imagem. Fonte: Elaborado pelos autores. 1.7.5 Cores de Imagens Existem diferentes modelos matemáticos que possibilitam a representação de imagens em um computador. Esses modelos são conhecidos como espaços de cor e são responsáveis por definir como as informações de cor são organizadas e interpretadas digitalmente. Nos tópicos a seguir, serão detalhadas as características de imagens: binárias, em tons de cinza e RGB. 1.7.6 Imagens Binárias Esta é a forma mais simples de representar uma imagem. Existem apenas dois valores possíveis para representar cada pixel de uma imagem binária: o valor 0 para a cor preta e o valor 1 (ou 255) para a cor branca. 22 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL Onde: (a) É válido lembrar que cada pixel de uma imagem representa a intensidade de luz capturada pelo sensor, correspondente a um ponto do objeto ou cena fotografada. (b) Consequentemente, para que imagens binárias possam ser registradas, um limiar deve ser definido, com a finalidade de determinar o valor analógico mínimo ou máximo registrado. (c) O limiar define se o pixel será representado como preto (levemente iluminado) ou branco (fortemente iluminado). (d) Nos Sistemas de Visão Computacional, esse tipo de imagem é bastante usado nas etapas de segmentação, assim como na extração de características. Segue abaixo um exemplo de uma imagem binária. Figura 1.17: Exemplo de imagem binária. Fonte: Adaptado de https://www.mathworks.com/matlabcentral/mlcdownloads/downloads/submissions/35158/versions/1/screenshot.JPG 1.7.7 Imagens em tons de cinza As imagens em tons de cinza vão além da capacidade das imagens binárias. Por meio delas, é possível armazenar valores intermediários, referentes à intensidade de luz, que necessariamente não precisam ser representados pela cor preta ou branca. As imagens em tons de cinza podem representar até 256 tons diferentes, variando de 0 (preto) até 255 (branco). Figura 1.18: Exemplo de imagem em tons de cinza. Fonte: Adaptado de https://craftofcoding.wordpress.com/wp-content/uploads/2017/02/lena.jpg?w=1024 23 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL Na Visão Computacional, imagens em tons de cinza são bastante usadas, uma vez que muitas vezes é necessário reconhecer ou contar objetos em imagens onde a cor do objeto não é uma informação relevante. Nessas situações, as imagens em tons de cinza são uma boa alternativa, pois tornam o processamento computacional mais rápido, visto que há um número menor de informações para processar e analisar. 1.7.8 Imagens coloridas em RGB Nas seções anteriores, demonstramos imagens binárias e em tons de cinza. Mas como o processo funciona com imagens coloridas? Apesar de existirem diversas formas de trabalhar com cores no ambiente digital, nas imagens digitais as cores são representadas, majoritariamente, pela combinação de três cores primárias aditivas: vermelho (Red), verde (Green) e azul (Blue). Esse modelo é conhecido como RGB, sigla originada do inglês Red, Green, Blue. Nesse sistema, cada pixel de uma imagem é composto por três canais de cor, correspondentes às intensidades de vermelho, verde e azul. A combinação de diferentes proporções desses três canais permite a formação de uma ampla gama de cores visíveis. Para se obter maior precisão na representação das cores e, consequentemente, uma gama mais ampla de tonalidades, os valores de intensidade de cada canal (vermelho, verde e azul) são representados em uma escala que varia de 0 a 255. Dessa forma, a cor vermelha pura é representada pelo triplo (255, 0, 0), indicando intensidade máxima no canal vermelho e ausência total de verde e azul. Já a cor amarela é formada pela combinação de intensidade máxima nos canais vermelho e verde, e zero no azul, sendo representada por (255, 255, 0). A imagem abaixo ilustra como o sistema RGB funciona na prática: Figura 1.19: Exemplo de imagem colorida. Fonte: Elaborado pelos autores. Conhecendo um pouco mais! Nas imagens coloridas, cada pixel pode ter uma combinação das cores primárias, ou seja, um pixel é formado por 3 valores. Assim, uma imagem colorida não é formada por uma matriz, mas por três matrizes que representam a porcentagem de cada cor básica em um pixel. 24 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL Copie e Teste! import cv2 import numpy as np # Carregando imagem colorida imagem = cv2.imread("frutas.jpg", cv2.IMREAD_COLOR) # Separa os canais na ordem B, G, R azul, verde, vermelho = cv2.split(imagem) # Exibindo imagens dos canais separados cv2.imshow("Imagem Original", imagem) cv2.imshow("Canal Vermelho", vermelho) cv2.imshow("Canal Verde", verde) cv2.imshow("Canal Azul", azul) cv2.waitKey(0) cv2.destroyAllWindows() O código acima carrega a imagem com a função imread(). Em seguida, são obtidas três matrizes, representando os canais de cores: azul, verde e vermelho, com a função split() da biblioteca OpenCV. Ao executar o código, deve ser gerada uma saída similar à apresentada na figura a seguir. Saída Esperada Figura 1.29: Exemplo de segmentação de canais BGR. Fonte: Elaborado pelos autores. Observe que ao exibir as imagens dos canais de forma individual com a função imshow(), as imagens aparecem em tons de cinza. Isso ocorre porque as matrizes resultantes são estruturas de 2 dimensões, com valores que podem variar de 0 a 255, o que é a característica de imagens em tons de cinza. Mas se você quiser visualizar os canais realmente nas cores primárias, devemos voltar para o plano de 3 dimensões, característico das imagens coloridas. Para isso, devemos definir os outros canais com zeros. Observe o código abaixo. Copie e Teste! import cv2 import numpy as np # Carregando imagem colorida 31 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL imagem = cv2.imread("frutas2.jpg", cv2.IMREAD_COLOR) # Separa os canais B, G, R azul, verde, vermelho = cv2.split(imagem) # Cria uma matriz de zeros com as mesmas dimensões da imagem zeros = np.zeros(imagem.shape[:2], dtype="uint8") # Exibindo imagens dos canais separados cv2.imshow("Imagem Original", imagem) cv2.imshow("Canal Vermelho", cv2.merge([zeros, zeros, vermelho])) cv2.imshow("Canal Verde", cv2.merge([zeros, verde, zeros])) cv2.imshow("Canal Azul", cv2.merge([azul, zeros, zeros])) cv2.waitKey(0) cv2.destroyAllWindows() Saída Esperada Figura 1.30: Exemplo de imagens segmentadas no espaço BGR. Fonte: Elaborado pelos autores. Conhecendo um pouco mais! A biblioteca OpenCV trata o espaço de cor como BGR (Blue, Green, Red), e não RGB. Por este motivo, quando utilizamos a função split(), declaramos as variáveis na sequência azul, verde, vermelho. Mesclando canais em uma imagem BGR Da mesma forma que segmentamos os canais de uma imagem colorida, podemos realizar a operação inversa, ou seja, juntar novamente esses canais resultando em uma única imagem colorida. Copie e Teste! import cv2 import numpy as np imagem = cv2.imread("frutas2.jpg") azul, verde, vermelho = cv2.split(imagem) # Combinando os três canais em uma única imagem imagemCombinada = cv2.merge((azul, verde, vermelho)) 32 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL cv2.imshow("Canal Azul", azul) cv2.imshow("Canal Verde", verde) cv2.imshow("Canal Vermelho", vermelho) cv2.imshow("Imagem Mesclada", imagemCombinada) cv2.waitKey(0) cv2.destroyAllWindows() Saída Esperada Figura 1.31: Exemplo de imagens mescladas no espaço BGR. Fonte: Elaborado pelos autores. 1.9.2 Convertendo imagens BGR para tons de cinza As imagens coloridas são representadas por um número maior de informações, visto que são necessárias 3 matrizes para a sua representação. Com o objetivo de reduzir a quantidade de informações a serem processadas, a conversão de imagens coloridas para imagens em tons de cinza é uma tarefa quase sempre realizada em sistemas de Visão Computacional. As imagens em tons de cinza, por outro lado, são representadas por uma única matriz e, na maioria das vezes, mantêm as características mais importantes para uma futura análise. Mas como podemos fazer essa conversão? Poderíamos realizar de forma manual, onde cada pixel seria o valor médio dos pixels correspondentes de cada canal, conforme ilustra a imagem a seguir. Figura 1.32: Exemplo didático da conversão de imagens coloridas em tons de cinza Fonte: Elaborado pelos autores. Seria um processo trabalhoso, não é? Mas como estamos trabalhando com a biblioteca OpenCV, a mesma já disponibiliza uma função que faz esse trabalho, a cvtColor(). 33 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL Copie e Teste! import cv2 as cv # Carregando imagem colorida imagemColorida = cv.imread("frutas2.jpg") # Convertendo a imagem de BGR para tons de cinza imagemCinza = cv.cvtColor(imagemColorida, cv.COLOR_BGR2GRAY) cv.imshow("Imagem Colorida", imagemColorida) cv.imshow("Imagem em Tons de Cinza", imagemCinza) cv.waitKey(0) cv.destroyAllWindows() No código acima, a função cvtColor() recebe como primeiro argumento a imagem a ser convertida. O segundo argumento é o código de conversão do espaço de cores. Como o OpenCV carrega imagens no padrão BGR por padrão, utilizamos o cv.COLOR_BGR2GRAY para indicar que a conversão deve ser feita de BGR para tons de cinza (GRAY). Saída Esperada Figura 1.33: Exemplo de imagem convertida para tons de cinza. Fonte: Elaborado pelos autores. 1.9.3 Curiosidades Extras Como já mencionado, imagens são matrizes. Aqui vamos apresentar um exemplo de como podemos criar imagens simplesmente usando números. Criando uma imagem com NumPy Copie e Teste! import cv2 import numpy as np import matplotlib.pyplot as plt 34 CAPÍTULO 1. FUNDAMENTOS DE VISÃO COMPUTACIONAL # Canais (Matplotlib espera RGB, então criamos nessa ordem) red = np.array([[225, 3, 5], [5, 5, 5], [7, 100, 3]], dtype=np. uint8) green = np.array([[255, 5, 5], [7, 8, 9], [2, 3, 4]], dtype=np. uint8) blue = np.array([[0, 5, 6], [2, 2, 6], [6, 5, 80]], dtype=np.uint8 ) # Imagem vazia com 3 dimensões (canais) matriz3D = np.zeros((3, 3, 3), dtype=np.uint8) # Adicionando os canais na ordem RGB para Matplotlib matriz3D[:, :, 0] = red matriz3D[:, :, 1] = green matriz3D[:, :, 2] = blue plt.figure(figsize=(2, 2)) plt.imshow(matriz3D) plt.show() Saída Esperada Figura 1.34: Exemplo de imagem criada com NumPy. Fonte: Elaborado pelos autores. 35 Capítulo 2 Pré-processamento e Segmentação de Imagens Iniciando o diálogo... Neste capítulo, serão abordadas as fases de pré-processamento de imagens e segmentação. A fase de pré-processamento tem como objetivo ”limpar”ou ”tratar”as imagens e realçar objetos de interesse. Já a fase de segmentação tem como objetivo separar áreas específicas nos objetos de interesse em uma nova imagem, excluindo também o segundo plano dessa região. 2.1 Pré-processamento No contexto da Visão Computacional, a fase de pré-processamento tem como objetivo realçar objetos de interesse em imagens, tornando sua segmentação mais eficaz. Diversas técnicas podem ser aplicadas para esse aprimoramento, incluindo operações aritméticas, transformações geométricas, ajustes de contraste e remoção de ruídos. Ao longo deste capítulo, exploraremos essas abordagens, trazendo exemplos práticos e discutindo os conceitos fundamentais, além dos tipos de ruído que podem afetar a qualidade das imagens. 2.1.1 Operações Básicas Na área de Visão Computacional e processamento de imagens, podemos realizar diversas operações, tais como: alterações de cores de imagens, operações aritméticas e operações geométricas, assim como análise de histogramas para verificar o nível de intensidade das cores que compõem uma imagem. Nesta seção, iremos apresentar como podemos obter valores de um determinado pixel, como alterar o valor de um pixel e como obter o tamanho (quantidade de pixels) de uma imagem. Obtendo os valores de um pixel Como uma imagem é representada por uma matriz, podemos acessar seus valores de forma similar a matrizes e vetores. Para isso, precisamos apenas informar a posição do pixel, ou seja, o número da linha e da coluna. 36 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS O código a seguir apresenta como podemos realizar essa ação de forma fácil e prática com o OpenCV. Copie e Teste! import cv2 import numpy as np imagem = cv2.imread("frutas2.jpg") pixelAlvo = imagem[0,0] print(f"Valor do pixel na posição (0,0): {pixelAlvo}") Após executar o código, deve aparecer um resultado similar ao apresentado abaixo. Os valores do pixel podem variar de acordo com a imagem que foi usada. Saída Esperada Valor do pixel na posição (0,0): [171 62 5] Observe que o valor retornado foi um array contendo três valores. Isso ocorre, pois, a imagem usada é colorida (no padrão BGR do OpenCV). Vamos realizar o mesmo procedimento, mas agora para uma imagem em tons de cinza. Neste caso, vamos primeiro converter a imagem para a escala de cinza e depois imprimir o valor de um determinado pixel. Copie e Teste! import cv2 import numpy as np imagem = cv2.imread("frutas2.jpg") imagemCinza = cv2.cvtColor(imagem, cv2.COLOR_BGR2GRAY) pixelAlvo = imagemCinza[0,0] print(f"Valor do pixel na posição (0,0): {pixelAlvo}") Após executar o código, deve aparecer um resultado similar ao apresentado abaixo. Saída Esperada Valor do pixel na posição (0,0): 57 Alterando os valores de um pixel Na seção anterior, vimos como obter os valores de um determinado pixel de uma imagem. Neste tópico, veremos como alterar os valores dos pixels. Para esta operação, basta atribuir valores à posição desejada. O código abaixo apresenta um exemplo de como podemos fazer isso. 37 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Copie e Teste! import cv2 import numpy as np imagem = cv2.imread("frutas2.jpg") imagemCinza = cv2.cvtColor(imagem, cv2.COLOR_BGR2GRAY) pixelAlvo = imagemCinza[0,0] print(f"Pixel na posição (0,0) antes: {pixelAlvo}") #alterando pixel na posição (0,0) imagemCinza[0,0] = 100 pixelAlvo = imagemCinza[0,0] print(f"Pixel na posição (0,0) depois: {pixelAlvo}") Após executar o código, deve aparecer um resultado similar ao apresentado abaixo. Saída Esperada Pixel na posição (0,0) antes: 57 Pixel na posição (0,0) depois: 100 Observe que é impresso em tela o valor do pixel na linha 0 e coluna 0. E logo depois, essa mesma posição recebe o valor 100. Caso Prático Para melhor fixação deste assunto, vamos realizar um caso de uso prático. No código apresentado no Capítulo 1, onde aprendemos a criar uma imagem a partir de uma matriz, vamos alterar as cores de alguns pixels. Copie e Teste! import cv2 import numpy as np import matplotlib.pyplot as plt red = np.array([[225,3,5],[5,5,5],[7,100,3]],dtype=np.uint8) green = np.array([[255,5,5],[7,8,9],[2,3,4]], dtype=np.uint8) blue = np.array([[0,5,6],[2,2,6],[6,5,80]], dtype=np.uint8) matriz3D = np.zeros((3,3,3),dtype=np.uint8) # Adicionando os canais (Matplotlib espera RGB) matriz3D[:,:,0] = red matriz3D[:,:,1] = green matriz3D[:,:,2] = blue plt.figure(figsize=(5,5)) 38 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS # Obtendo e imprimindo pixel na posição (0,0) print(f"Pixel na posição (0,0): {matriz3D[0,0]}") plt.imshow(matriz3D) plt.show() Após executar o código, deve aparecer o resultado similar ao apresentado abaixo. Saída Esperada Pixel na posição (0,0): [225 255 0] Figura 2.1: Exemplo de imagem criada com NumPy. Fonte: Elaborado pelos autores. Veja que agora estamos imprimindo os valores do pixel presente na linha 0 e coluna 0 da matriz que representa a imagem com a instrução print(f"Pixel na posição (0,0): matriz3D[0,0]"). Observe que se trata de um pixel de cor amarela, conforme podemos ver na imagem. Vamos trocar esses valores para [255,0,0], que representa a cor vermelha. O código abaixo apresenta como podemos fazer isso. Observe o comando a seguir: matriz3D[0,0]=[255,0,0], que atribui o valor RGB para a cor vermelha pura na posição (0,0) da nossa imagem. Copie e Teste! import cv2 import numpy as np import matplotlib.pyplot as plt red = np.array([[225,3,5],[5,5,5],[7,100,3]],dtype=np.uint8) green = np.array([[255,5,5],[7,8,9],[2,3,4]], dtype=np.uint8) blue = np.array([[0,5,6],[2,2,6],[6,5,80]], dtype=np.uint8) matriz3D = np.zeros((3,3,3),dtype=np.uint8) # Adicionando os canais matriz3D[:,:,0] = red 39 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS matriz3D[:,:,1] = green matriz3D[:,:,2] = blue # Alterando a cor presente na posição (0,0) matriz3D[0,0] = [255,0,0] plt.figure(figsize=(5,5)) plt.imshow(matriz3D) plt.show() Veja na saída agora que a imagem tem seu primeiro pixel na cor vermelha. Saída Esperada Figura 2.2: Exemplo de imagem criada com NumPy e OpenCV. Fonte: Elaborado pelos autores. 2.1.2 Acessando Informações de Imagens Acessar informações básicas sobre uma imagem, como o número de linhas, colunas, canais e a quantidade total de pixels, é uma etapa fundamental em Visão Computacional. Essas propriedades fornecem detalhes sobre a estrutura e o conteúdo da imagem, permitindo a configuração adequada de algoritmos e a verificação de compatibilidade entre imagens em operações como adição ou subtração. Definição Uma imagem digital é representada como uma matriz de pixels. As principais informações que podem ser acessadas incluem: •Número de Linhas (H): A altura da imagem, correspondente ao número de linhas na matriz. •Número de Colunas (W): A largura da imagem, correspondente ao número de colunas na matriz. 40 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS plt.show() Saída Esperada Figura 2.7: Ilustração do resultado da mesclagem de imagens. Fonte: Adaptado de https://www.nicepng.com/ourpic/u2q8i1i1i1i1a9t4_mercedes-amg-c63-coupe-png/. 2.1.4 Operações Geométricas As operações geométricas em visão computacional modificam a disposição espacial dos pixels de uma imagem, permitindo transformações como inversão, rotação, redimensionamento e ajustes de perspectiva. Operação de Rotação A rotação de imagens é uma operação geométrica que altera a orientação de uma imagem, girando-a em torno de um ponto (geralmente o centro) por um ângulo especificado. Para realizar essa operação, usamos as funções getRotationMatrix2D() ewarpAffine() da biblioteca OpenCV. A função getRotationMatrix2D() recebe três parâmetros: (a) center: O centro da rotação (geralmente o centro da imagem). (b) angle: O ângulo de rotação em graus. (c) scale: O fator de escala (1.0 para manter o tamanho original). Após obter a matriz de rotação, geramos a imagem rotacionada com a função warpAffine(), que recebe a imagem original, a matriz de rotação e o tamanho da imagem de saída. Copie e Teste! import cv2 as cv import numpy as np from matplotlib import pyplot as plt imgOriginal = cv.imread("einstein.jpg", 0) totalLinhas, totalColunas = imgOriginal.shape # Obtém a matriz de rotação mat = cv.getRotationMatrix2D((totalColunas/2, totalLinhas/2), 90, 1) 47 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS # Rotaciona a imagem imgRotacionada = cv.warpAffine(imgOriginal, mat, (totalColunas, totalLinhas)) fig = plt.figure(figsize=(10,8)) ax1 = fig.add_subplot(121) plt.title('Imagem Original') plt.imshow(imgOriginal, cmap='gray') ax2 = fig.add_subplot(122) plt.imshow(imgRotacionada, cmap='gray') plt.title('Imagem Rotacionada') plt.show() Saída Esperada Figura 2.8: Ilustração da rotação de uma imagem em 90 graus. Fonte: https://segredosdomundo.r7.com/wp-content/uploads/2015/10/destaque10.jpg.webp. Ajuste de Escala (Redimensionamento) O ajuste de escala, ou redimensionamento, é uma operação geométrica que altera as dimensões de uma imagem, aumentando ou reduzindo sua largura e altura. O OpenCV possui a função resize(), que permite realizar o ajuste de escala. Obrigatoriamente, a função resize() deve receber os seguintes parâmetros: (a) src: Matriz referente à imagem. (b) dsize: Tamanho da imagem de saída (ou None se usar fx e fy). (c) fx: Fator de escala horizontal. (d) fy: Fator de escala vertical. 48 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS (e) interpolation: Método de interpolação para calcular os novos pixels. Veja o código abaixo que apresenta um exemplo de redimensionamento de imagens. Neste exemplo, iremos diminuir a imagem em 50%. Copie e Teste! import cv2 as cv import numpy as np from matplotlib import pyplot as plt imgOriginal = cv.imread("einstein.jpg") imgModificada = cv.resize(imgOriginal, None, fx=0.5, fy=0.5, interpolation=cv.INTER_CUBIC) cv.imshow("Imagem Original", imgOriginal) cv.imshow("Imagem Modificada", imgModificada) cv.waitKey(0) cv.destroyAllWindows() Saída Esperada Figura 2.9: Ilustração do resultado do redimensionamento de uma imagem. Fonte: https://segredosdomundo.r7.com/wp-content/uploads/2015/10/destaque10.jpg.webp. 49 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS 2.1.5 Análise de Histogramas O histograma de cores de uma imagem representa a distribuição de frequência dos níveis de intensidade de cinza (ou de cada canal de cor) em relação à quantidade de pixels. Essa distribuição revela informações cruciais sobre a qualidade visual da imagem, particularmente no que se refere ao brilho (intensidade luminosa) e ao contraste. Por exemplo, um histograma concentrado em valores baixos indica uma imagem escura, enquanto picos em uma faixa estreita sugerem baixo contraste. O processo de encontrar o histograma de uma imagem nos permite ter acesso a informações sobre a qualidade da imagem, principalmente à intensidade luminosa и ao contraste que a imagem carrega. O histograma de uma imagem indica o número ou percentual de pixels que a imagem tem em um determinado nível de cor ou de cinza. Para se obter o histograma de uma imagem, podemos usar a função hist() da biblioteca Matplotlib. A função hist() recebe como entrada os seguintes parâmetros: (a) src: Matriz referente à imagem. (b) bins: Número de elementos distintos que podem ser representados. (c) range: Intervalo entre os elementos. Copie e Teste! import cv2 as cv import numpy as np from matplotlib import pyplot as plt # Carrega a imagem em escala de cinza img = cv.imread("Cinza.jpg") # Criação da figura plt.figure(figsize=(12, 5)) # Subplot 1: imagem plt.subplot(1, 2, 1) plt.imshow(img, cmap='gray') plt.title("Imagem em Tons de Cinza") plt.axis("off") # Subplot 2: histograma plt.subplot(1, 2, 2) plt.hist(img.ravel(), bins=256, range=[0, 256]) plt.title("Histograma") plt.xlabel("Intensidade") plt.ylabel("Número de Pixels") plt.tight_layout() plt.show() O método ravel() é uma função que transforma um array multidimensional (matriz) em um array unidimensional (lista 1D). Ele é particularmente útil quando você precisa trabalhar 50 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS com todos os elementos de uma matriz (como uma imagem) em uma única sequência linear, por exemplo, para calcular histogramas ou estatísticas. O método ravel() foi aplicado na imagem para transformar a matriz que a representa em uma lista de valores. Ao observar a função hist(), vimos que passamos a imagem, o parâmetro bins (que pode assumir 256 valores diferentes) e o range, que define quais valores os 256 bins podem assumir, neste caso, qualquer valor que esteja entre 0 e 255. Ao observar a imagem gerada na saída abaixo, visualmente podemos afirmar que ela é predominantemente cinza. Analisando pelo histograma, visto que uma imagem em tons de cinza tem seus pixels variando de 0 (preto) a 255 (branco), e pixels com valores próximos a 128 (média) serão predominantemente cinzas. Pela leitura do gráfico, pode-se destacar que a maioria dos pixels está entre 100 e 180. Com essa grande concentração, a imagem terá uma cor predominante cinza. Saída Esperada Figura 2.10: Ilustração de uma imagem em tons de cinza e seu respectivo histograma. Fonte: https://olivre.com.br/ciclista-dia-nacional-alerta-sobre-desafios-para-transitomais-seguro. Histograma de imagens binárias Para se calcular o histograma de uma imagem binária, o processo é o mesmo que para qualquer outra imagem. Veja a imagem abaixo que, apesar de ter algumas áreas brancas, é predominantemente preta. Figura 2.11: Imagem ilustrativa com números. Fonte: Elaborado pelos autores. 51 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS O código abaixo calcula e mostra o histograma da imagem. Observe que o código é similar ao já apresentado, mudando apenas a imagem de origem. Copie e Teste! import cv2 as cv import numpy as np from matplotlib import pyplot as plt # Carrega a imagem img = cv.imread("numeros.png") # Criação da figura plt.figure(figsize=(12, 5)) # Subplot 1: imagem plt.subplot(1, 2, 1) plt.imshow(img, cmap='gray') plt.title("Imagem Binária") plt.axis("off") # Subplot 2: histograma plt.subplot(1, 2, 2) plt.hist(img.ravel(), bins=256, range=[0, 256]) plt.title("Histograma") plt.xlabel("Intensidade") plt.ylabel("Número de Pixels") plt.show() Saída Esperada Figura 2.12: Ilustração do histograma de uma imagem binária. Fonte: Elaborado pelos autores. Ao analisar o histograma da imagem, como se trata de uma imagem binária, seus pixels só podem ter os valores 0 (preto) ou 255 (branco). Como podemos observar, a imagem é na sua maioria representada pela cor preta, e o histograma mostra claramente a distinção entre os pixels pretos e brancos. 52 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Equalização de Histogramas O histograma de uma imagem pode nos dar informações importantes. Imagens com alta exposição à luz geralmente têm seus histogramas com alta concentração de pixels à direita (próximos ao branco), já para imagens capturadas com baixa exposição à luz, os pixels se concentram à esquerda. Figura 2.13: Comparação entre histogramas de imagens escuras, normais e claras. Fonte: Adaptado de [2]. Observe a figura abaixo, que exemplifica uma imagem capturada em um ambiente com baixa luminosidade. Figura 2.14: Exemplo de imagem com baixa luminosidade. Fonte: https://www.math.univ-paris13.fr/ chaussar/Teaching/2016-2017/Master2/03 - Histogrammes - PDF Couleur.pdf Ao obter o histograma da imagem, temos o seguinte resultado. Nele, podemos ver que, por se tratar de uma imagem escura, a maioria dos pixels está concentrada à esquerda, ou seja, com valores próximos a 0, que é a característica principal de imagens com baixa luminosidade. 53 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Figura 2.15: Histograma de imagem com baixa luminosidade. Fonte: Elaborada pelos autores. O objetivo de equalizar o histograma de uma imagem é redistribuir as tonalidades dos pixels, de modo a abranger uma faixa maior de intensidades e tornar a distribuição de dados do histograma mais uniforme. Isso tornará a imagem mais nítida, contendo pixels com tonalidades de cinza que variam de 0 a 255 de forma mais balanceada. Conhecendo um pouco mais! A equalização de histograma busca uma distribuição uniforme. Para entender outro tipo de distribuição estatística muito comum, a distribuição Normal (ou Gaussiana), acesse a página web: http://www.youtube.com/watch?v=CcIhlbN1U9Q, que aborda o assunto. Para equalizar o histograma de uma imagem, podemos usar a função equalizeHist() do OpenCV. A função recebe apenas a imagem em tons de cinza que se deseja equalizar e retorna uma nova imagem com o histograma equalizado. No código abaixo, vamos ler uma imagem, equalizar seu histograma e apresentar a imagem original, a imagem equalizada, assim como seus respectivos histogramas. Copie e Teste! import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread("SemLuz.png", cv2.IMREAD_GRAYSCALE) imgEqualizada = cv2.equalizeHist(img) fig = plt.figure(figsize=(20,5)) 54 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS ax1 = fig.add_subplot(121) plt.imshow(img, cmap=plt.cm.gray) plt.title("Imagem Original") ax2 = fig.add_subplot(122) plt.imshow(imgEqualizada, cmap=plt.cm.gray) plt.title("Imagem Equalizada") figHistograma = plt.figure(figsize=(20,5)) ax1 = figHistograma.add_subplot(121) plt.hist(img.ravel(), bins=256, range=[0,255], color='black') plt.title("Histograma Original") ax2 = figHistograma.add_subplot(122) plt.hist(imgEqualizada.ravel(), bins=256,range=[0,255], color=' black') plt.title("Histograma Equalizado") plt.show() Saída Esperada Figura 2.16: Ilustração do resultado da equalização de histograma. Fonte: Elaborado pelos autores. Ao analisar as saídas, observe as imagens à esquerda com características escuras, não sendo possível enxergar os rostos das pessoas. À direita, a imagem equalizada, onde é possível ver os rostos de forma nítida, e seu histograma, no qual os pixels foram distribuídos uniformemente. 55 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS 2.1.6 Ruídos em Imagens Ruídos em imagens digitais são variações indesejadas nos valores dos pixels que degradam a qualidade visual ou dificultam o processamento automatizado. Essas variações podem surgir durante a captura, transmissão ou armazenamento de imagens, ou ser procedentes dos processos de ajustes de perspectiva e escala, assim como das operações aritméticas e da equalização de histogramas, e são um desafio comum em Visão Computacional. Compreender as causas e os tipos de ruídos é essencial para desenvolver técnicas de pré-processamento e filtragem que melhorem a qualidade das imagens. Os ruídos em imagens podem ser causados por diversos fatores, incluindo: (a) Sensores de Câmeras: Imperfeições em sensores (ex.: CCD ou CMOS) ou condições de baixa iluminação podem introduzir ruídos, como ruído térmico ou de leitura. (b) Condições Ambientais: Iluminação inadequada, interferências eletromagnéticas ou vibrações podem afetar a captura da imagem. (c) Compressão e Transmissão: Algoritmos de compressão (ex.: JPEG) ou falhas na transmissão de dados podem gerar artefatos que se manifestam como ruídos. (d) Processamento Digital: Operações como redimensionamento ou conversão de formatos podem introduzir ruídos devido a erros de arredondamento ou interpolação. A seguir, serão apresentados os principais tipos de ruídos. Ruídos de captura Ruídos de captura são imperfeições ou distorções indesejadas que aparecem em uma imagem ou vídeo durante o processo de aquisição. Podem ser provocadas por poeira no ambiente, vibração da câmera, distorção da lente, iluminação inadequada e ruído elétrico no sensor. Figura 2.17: Exemplo de imagem com ruído de captura. Fonte: Wikimedia Commons. Ruído Sal e Pimenta Também conhecido como ruído impulsivo, é caracterizado pela adição aleatória de pixels pretos e brancos na imagem. Este tipo de ruído não é muito comum nas câmeras com sensores modernos. 56 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Conhecendo um pouco mais! Embora o filtro Gaussiano possa ser aplicado a qualquer imagem para criar um efeito de desfoque, sua principal força é na remoção de ruído Gaussiano. Para o ruído ”sal e pimenta”do exemplo, o Filtro de Mediana (que será visto a seguir) geralmente produz resultados superiores. Copie e Teste! import cv2 as cv from matplotlib import pyplot as plt img = cv.imread('001.png') # Mascara mascara = (5,5) # Grau de suavização grauSuavizacao = 0 imgGauss = cv.GaussianBlur(img, mascara, grauSuavizacao) fig = plt.figure(figsize=(10,5)) ax1 = fig.add_subplot(121) plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)) plt.title("Imagem original com ruído") ax2 = fig.add_subplot(122) plt.imshow(cv.cvtColor(imgGauss, cv.COLOR_BGR2RGB)) plt.title("Imagem com Filtro Gaussiano") plt.show() Saída Esperada Figura 2.27: Ilustração do resultado do filtro Gaussiano em imagem com ruído. Fonte: Adaptado de https://ramon-gzz.blogspot.com/2013/02/actividad-2-ruido-sal-y-pimienta.html. Observe que, ao aplicar o filtro, os ruídos foram atenuados. Como o parâmetro do des63 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS vio padrão (sigmaX) foi definido como 0, a intensidade da suavização foi calculada automaticamente a partir do tamanho da máscara (ksize). Isso permitiu preservar o conteúdo de alta frequência (as bordas) de forma mais eficaz que no filtro de média. Sinta-se à vontade para fornecer um valor explícito e maior que 0 para sigmaX e verificar como o desfoque se torna mais intenso. Filtro de Mediana O filtro de mediana é um filtro não linear que apresenta excelentes resultados no tratamento de ruído do tipo ”sal e pimenta”(salt-and-pepper). Ao ser comparado com o filtro Gaussiano, o filtro de mediana é mais eficaz na preservação de detalhes de alta frequência, como bordas e contornos. Matematicamente, esse filtro atua substituindo o valor de cada pixel pela mediana estatística dos valores dos pixels em sua vizinhança. Sua aplicação com a biblioteca OpenCV pode ser realizada pelo método medianBlur(). O método medianBlur() recebe dois parâmetros obrigatórios: (a) src: A imagem de entrada (obrigatoriamente de 8 bits e 1, 3 ou 4 canais). (b) ksize: O tamanho da abertura do kernel. Este valor deve ser um inteiro ímpar e maior que 1, como por exemplo 3,5ou 7. Este número define o tamanho da vizinhança a ser considerada para o cálculo da mediana (ex: um valor 5corresponde a uma máscara de 5x5 pixels). Copie e Teste! import cv2 as cv from matplotlib import pyplot as plt imagemOriginal = cv.imread('head.png') grauSuavizacao = 3 imgFiltrada = cv.medianBlur(imagemOriginal, grauSuavizacao) fig = plt.figure(figsize=(10,5)) ax1 = fig.add_subplot(121) plt.imshow(cv.cvtColor(imagemOriginal, cv.COLOR_BGR2RGB)) plt.title("Imagem original com ruído") ax2 = fig.add_subplot(122) plt.imshow(cv.cvtColor(imgFiltrada, cv.COLOR_BGR2RGB)) plt.title("Imagem com filtro Mediana") plt.show() 64 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.28: Ilustração do resultado da aplicação do filtro de mediana. Fonte: Adaptado de https://pyscience.wordpress.com/wp-content/uploads/2014/09/wpidmyheadslice.png?w=584. 2.1.8 Realce de Bordas Detectar bordas é uma tarefa, muitas vezes, não trivial, mas útil, porque nos ajuda a segmentar objetos de uma forma muito direta em algumas situações. Mas o que é, na verdade, uma borda? Podemos dizer que uma borda é um “espaço geométrico” que delimita um objeto qualquer. Infelizmente, essa definição é pouco precisa e pouco útil na maioria dos problemas. E por quê? Porque o que queremos é encontrar as bordas e, só depois, delimitar o objeto. Então, precisamos encontrar outra forma de definir uma borda para o computador. E como podemos encontrar as bordas? Uma forma é encontrar os pontos em que a intensidade luminosa muda de forma abrupta. Veja a imagem abaixo. Figura 2.29: Ilustração de imagem com borda. Fonte: Elaborado pelos autores. Observe a imagem acima: temos um pedaço preto que, de repente, fica branco. É justamente essa mudança na cor que nos faz identificar uma borda. Porém, ao se observar mais atentamente, essa mudança ocorre de acordo com a figura a seguir. 65 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Figura 2.30: Ilustração de bordas com gradiente. Fonte: Elaborado pelos autores. Temos novamente a mudança de cores do preto para o branco. Entretanto, não identificamos uma borda porque essa mudança não é “abrupta”. O gradiente é um vetor que aponta para a direção em que uma função tem o maior aumento. Ou seja, se em um ponto esse gradiente é muito grande, quer dizer que ali há uma grande taxa de variação no valor da função. Para achar essas variações significativas de cores, usamos máscaras de tipos específicos para realizar convolução. Para encontrar bordas em uma imagem, temos alguns operadores no OpenCV, tais como: (a) Operador ou Filtro de Sobel. (b) Operador Laplaciano. (c) Detector de Bordas de Canny. Neste material, iremos apresentar o detector de bordas de Canny, que utiliza um algoritmo multiestágios para detectar uma ampla gama de bordas em imagens. O detector de bordas de Canny pode ser resumido nos passos descritos na imagem abaixo. Figura 2.31: Passo a passo do algoritmo de Canny. Fonte: Elaborado pelos autores. Podemos usar o detector de bordas de Canny com OpenCV através da função Canny(), que recebe três parâmetros como entrada: 66 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS (a) Imagem Original: Imagem onde queremos encontrar as bordas. (b) Limiar 1: Valor mínimo de intensidade do gradiente para considerar um pixel como candidato a borda. (c) Limiar 2: Valor máximo de intensidade do gradiente para considerar um pixel como borda forte. Abaixo é apresentado um exemplo de como podemos detectar bordas em uma imagem. Copie e Teste! import cv2 #lendo a imagem img = cv2.imread('predio.JPG') #Aplicando o detector de bordas de Canny canny = cv2.Canny(img, 100, 200) cv2.imshow('Original', img) cv2.imshow('Detector de Bordas', canny) cv2.waitKey(0) cv2.destroyAllWindows() Saída Esperada Figura 2.32: Ilustração de imagem com bordas detectadas com o algoritmo de Canny. Fonte: Adaptado de https://i.sstatic.net/Q3YJA.jpg. 67 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS 2.1.9 Operações Morfológicas Figura 2.33: Ilustração de letras com falhas. Fonte: Adaptado de [9] Ao analisar a imagem acima, podemos presenciar algumas falhas na segunda letra J, o que pode ocasionar problemas em uma fase de extração de características ou tarefas de reconhecimento, dentre outras atividades. Na área de Visão Computacional, as operações morfológicas são ferramentas especiais que podemos usar para alterar as formas e estruturas nessas imagens. O que fazemos com as operações morfológicas é manipular os pixels para que se unam ou para remover pixels indesejados, alterando a aparência dos objetos na imagem. Essas operações podem ser aplicadas a qualquer tipo de imagem, entretanto, são frequentemente utilizadas em imagens binárias, com a finalidade de realçar o objeto de interesse ou tratar ruídos provocados pelo processo de binarização. Antes de abordar as operações morfológicas, precisamos conhecer os elementos estruturantes. Elementos Estruturantes Um elemento estruturante é uma máscara (kernel) para aplicação de operações morfológicas. Esse elemento é uma matriz, com valores 0 e 1, que percorrerá toda a imagem a ser tratada. Nesse processo, uma determinada operação morfológica será realizada pixel a pixel, alterando o valor de cada um para seguir o padrão definido por esse elemento. A figura seguinte ilustra três padrões comumente utilizados: retangular, elipse e cruz. Figura 2.34: Ilustração de elementos estruturantes. Fonte: Elaborado pelos autores. 68 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS A biblioteca OpenCV já possui nativamente esses três elementos. Para obtê-los, chamamos a função getStructuringElement() e passamos como parâmetro o tipo que desejamos e o tamanho da máscara. Ela recebe dois parâmetros principais: a forma e o tamanho do kernel desejado. (a) shape: O formato do elemento estruturante. Os mais comuns são cv.MORPH_RECT (retangular), cv.MORPH_ELLIPSE (elíptico) e cv.MORPH_CROSS (em forma de cruz). (b) ksize: O tamanho do kernel, fornecido como uma tupla (largura, altura). O código para gerar esses três tipos de elementos com um tamanho de 5x5 pixels produziria a seguinte saída: Copie e Teste! import numpy as np import cv2 retangulo = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) elipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) cruz = cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5)) print("Retangulo:") print(retangulo) print("\nElipse:") print(elipse) print("\nCruz:") print(cruz) Saída Esperada Figura 2.35: Ilustração dos três principais tipos de elementos estruturantes. Fonte: Elaborado pelos autores. Note na saída acima como a matriz de 1s e 0s define a forma exata de cada elemento. É essa ”forma”que será deslizada sobre a imagem de entrada para realizar as operações morfológicas. O elemento estruturante define a vizinhança de pixels que será afetada em cada passo da operação. 69 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS 2.1.10 Erosão e Dilatação Erosão e dilatação são operações morfológicas fundamentais em visão computacional, aplicadas principalmente a imagens binárias ou em tons de cinza para modificar a forma dos objetos. Elas servem como base para muitas outras operações morfológicas mais complexas. Erosão A operação de erosão reduz o tamanho dos objetos em uma imagem ao remover pixels de suas bordas. Ela é particularmente útil para eliminar pequenos ruídos (como pontos brancos isolados), separar objetos que estão sutilmente conectados e refinar os contornos de um objeto principal. O processo utiliza um elemento estruturante (kernel) que desliza sobre a imagem. Um pixel na imagem de saída é mantido (com valor 1) somente se o elemento estruturante se encaixar perfeitamente na vizinhança do pixel correspondente na imagem de entrada [9]. Esse efeito de ”corrosão”das bordas é ilustrado na figura abaixo. Figura 2.36: Ilustração da operação de erosão com um elemento estruturante 3x3. Fonte: [2]. A erosão pode ser aplicada em uma imagem com o OpenCV usando a função erode(). Seus principais parâmetros são: (a) src: A imagem de entrada que será submetida ao processo de erosão. (b) kernel: O elemento estruturante a ser usado. Este é o kernel que criamos com a função cv.getStructuringElement() (pode ser retangular, elíptico, etc.). (c) iterations: O número de vezes que a operação será aplicada. O valor padrão é 1. Copie e Teste! import numpy as np import cv2 from matplotlib import pyplot as plt imagemOriginal = cv2.imread("letra-J-com-ruidos.png") elemento_estruturante = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) imagem_processada = cv2.erode(imagemOriginal, 70 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS elemento_estruturante, iterations = 1) figura = plt.figure(figsize=(10,5)) eixo1 = figura.add_subplot(121) plt.imshow(cv2.cvtColor(imagemOriginal, cv2.COLOR_BGR2RGB)) plt.title("Imagem Original") eixo2 = figura.add_subplot(122) plt.imshow(cv2.cvtColor(imagem_processada, cv2.COLOR_BGR2RGB)) plt.title("Imagem com Erosão") plt.show() Saída Esperada Figura 2.37: Ilustração do resultado de erosão. Fonte: Adaptado de [9]. No código apresentado, foi utilizado um elemento estruturante retangular com um kernel (núcleo) de dimensões 5x5. É importante notar que tanto o tamanho do kernel quanto o número de iterations (interações) são parâmetros que podem ser ajustados. Recomendamos que você experimente com valores diferentes, por exemplo, um kernel menor como (3,3) ou um número maior de iterações para observar como o resultado se adequa melhor ao seu problema. Dilatação A operação de dilatação é o oposto da erosão. Seu efeito é aumentar a área dos objetos de interesse em uma imagem, adicionando pixels às suas bordas. Ela é útil para preencher pequenos buracos ou lacunas em um objeto, conectar componentes que estão próximos e engrossar os contornos. O processo também utiliza um elemento estruturante (kernel). A regra da dilatação é: ao deslizar o kernel sobre a imagem, um pixel na imagem de saída é ativado (recebe valor 1) se pelo menos um dos pixels do kernel se sobrepuser a um pixel ativado na imagem de entrada. Esse efeito de expansão das bordas é ilustrado na figura a seguir. 71 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Figura 2.38: Ilustração do processo de dilatação com um elemento estruturante 3x3. Fonte: Adaptado de [2]. A dilatação pode ser aplicada em uma imagem com o OpenCV usando a função dilate(). Seus parâmetros são idênticos aos da função de erosão: (a) src: A imagem de entrada que será submetida ao processo de dilatação. (b) kernel: O elemento estruturante a ser usado, criado com a função cv.getStructuringElement(). (c) iterations: O número de vezes que a operação será aplicada consecutivamente. O valor padrão é 1. Copie e Teste! import numpy as np import cv2 from matplotlib import pyplot as plt imagemOriginal = cv2.imread('letraJ.png') elemento_estruturante = cv2.getStructuringElement(cv2. MORPH_ELLIPSE, (5,5)) imagem_processada = cv2.dilate(imagemOriginal, elemento_estruturante, iterations = 1) figura = plt.figure(figsize=(10,8)) eixo1 = figura.add_subplot(121) plt.imshow(cv2.cvtColor(imagemOriginal, cv2.COLOR_BGR2RGB)) plt.title("Imagem Original") eixo2 = figura.add_subplot(122) plt.imshow(cv2.cvtColor(imagem_processada, cv2.COLOR_BGR2RGB)) plt.title("Imagem com Dilatação") plt.show() 72 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.46: Ilustração do resultado da dilatação de imagens. Fonte: Adaptado de [2]. Ao se aplicar a operação de Fechamento, o algoritmo irá realizar a dilatação (fechando as falhas) e, em seguida, a erosão, voltando a imagem para sua estrutura inicial. A função morphologyEx() permite realizar esse tratamento apenas trocando o tipo de operação para cv2.MORPH_CLOSE. Copie e Teste! import numpy as np import cv2 from matplotlib import pyplot as plt imagemOriginal = cv2.imread('rodasFalhas.png') elementoEstruturante = cv2.getStructuringElement(cv2.MORPH_ELLIPSE , (5,5)) imagemProcessada = cv2.morphologyEx(imagemOriginal, cv2. MORPH_CLOSE, elementoEstruturante) figura = plt.figure(figsize=(10,8)) ax1 = figura.add_subplot(121) plt.imshow(cv2.cvtColor(imagemOriginal, cv2.COLOR_BGR2RGB)) plt.title("Imagem Original") ax2 = figura.add_subplot(122) plt.imshow(cv2.cvtColor(imagemProcessada, cv2.COLOR_BGR2RGB)) plt.title("Imagem com Fechamento") plt.show() Veja que as falhas da imagem foram corrigidas e sua estrutura original foi mantida, por meio do processo de Fechamento. 79 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.47: Ilustração do resultado do processo de Fechamento. Fonte: Adaptado de [2]. Nesta seção, aprendemos algumas técnicas de pré-processamento de imagens, tais como: operações aritméticas e geométricas, filtros de suavização e operações morfológicas. Todas essas operações têm como objetivo preparar as imagens para as próximas etapas. A próxima seção nos mostrará como podemos segmentar imagens, ou seja, separar da imagem como um todo somente a parte que nos interessa. 2.2 Segmentação Figura 2.48: Ilustração de imagens segmentadas. Fonte: Wikimedia Commons A segmentação de objetos em imagens é uma etapa fundamental em sistemas de Visão Computacional, sendo responsável por isolar exclusivamente a região correspondente ao objeto de interesse (primeiro plano ou foreground), eliminando componentes do fundo (background). Esse procedimento viabiliza a análise detalhada e a extração de atributos representativos do objeto, sendo crucial para aplicações que exigem reconhecimento preciso. Ao longo desta seção, serão apresentados alguns dos principais métodos de segmentação, iniciando pelo processo de binarização, técnica que se baseia na distinção dos pixels por limiarização para realçar estruturas relevantes na imagem. 80 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS 2.2.1 Segmentação por Binarização (Limiarização) A binarização é uma técnica de segmentação que converte uma imagem em tons de cinza para uma imagem binária, onde cada pixel assume apenas um de dois valores (geralmente 0 para preto e 255 para branco). O processo simplifica a imagem, destacando objetos de interesse com base em um limiar (threshold). Para uma imagem em escala de cinza I(x, y)com intensidades no intervalo [0,255], a binarização com um limiar Lé definida como: Ib(x, y) = {255 se I(x, y)> L 0caso contrário Vamos entender o processo. Para a imagem abaixo, definimos um limiar Lcom o valor 120. Para cada pixel, a regra é aplicada: se o valor do pixel for maior que 120, ele se torna branco (255); caso contrário, torna-se preto (0). Figura 2.49: Ilustração do processo de binarização por limiar. Fonte: Elaborado pelos autores. Binarização por Limiar Simples Para realizar o processo de binarização com OpenCV, usamos a função threshold(), que recebe os seguintes parâmetros: (a) src: A imagem de entrada em tons de cinza. (b) thresh: O valor do limiar (0-255), geralmente definido por tentativa e erro. (c) maxval: O valor máximo a ser atribuído aos pixels que excedem o limiar (geralmente 255). (d) type: O tipo de binarização a ser aplicado. Os mais comuns são: –cv2.THRESH_BINARY: Se o pixel > limiar, ele recebe maxval; senão, recebe 0. (Objeto fica branco). –cv2.THRESH_BINARY_INV: Se o pixel > limiar, ele recebe 0; senão, recebe maxval. (Objeto fica preto). Veja abaixo o código que realiza essa operação. Atente que para o processo de binarização é necessária uma imagem em tons de cinza. 81 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Copie e Teste! import cv2 import numpy as np from matplotlib import pyplot as plt # Carrega a imagem diretamente em tons de cinza imgOriginal = cv2.imread('lenaCinza.jpg', 0) # Variáveis do processo LIMIAR = 110 VALOR_MAXIMO = 255 metodo = cv2.THRESH_BINARY # A função threshold retorna o limiar usado e a imagem binarizada retorno, imgBinarizada = cv2.threshold(imgOriginal, LIMIAR, VALOR_MAXIMO, metodo) # Exibição com Matplotlib figura = plt.figure(figsize=(10, 5)) eixo1 = figura.add_subplot(121) plt.imshow(imgOriginal, cmap='gray') plt.title("Imagem Original") eixo2 = figura.add_subplot(122) plt.imshow(imgBinarizada, cmap='gray') plt.title("Imagem Binarizada") plt.show() Ao executar o código, devemos ter uma saída similar à apresentada na imagem abaixo. Mais uma vez, atenção: o limiar depende muito da imagem, e você pode ir alterando o valor até encontrar um resultado satisfatório. 82 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.50: Ilustração do resultado do processo de binarização. Fonte: Adaptada de https://onlinelibrary.wiley.com/doi/10.1155/2010/914564 Binarização Adaptativa A binarização adaptativa é uma técnica avançada que calcula o limiar para cada pixel com base nas intensidades de sua vizinhança, sendo ideal para imagens com iluminação não uniforme. O limiar T(x, y)para cada pixel é calculado como a média (ou média ponderada Gaussiana) da vizinhança, subtraída de uma constante C. Ib(x, y) = {255 se I(x, y)> T (x, y) 0caso contrário onde T(x, y) = média(N(x, y)) −C Na imagem abaixo, observe que a binarização simples falhou na região inferior devido à baixa iluminação. Figura 2.51: Exemplo de falha na binarização simples devido à iluminação. Fonte: Adaptada de [2]. 83 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Para essas situações, a binarização adaptativa apresenta bons resultados. O OpenCV disponibiliza a função adaptiveThreshold() para essa tarefa. Seus parâmetros são: (a) src: Imagem de entrada em tons de cinza. (b) maxValue: Valor máximo a ser atribuído (255). (c) adaptiveMethod:ADAPTIVE_THRESH_MEAN_C ou ADAPTIVE_THRESH_GAUSSIAN_C. (d) thresholdType: Tipo de binarização (ex: THRESH_BINARY). (e) blockSize: Tamanho da vizinhança para calcular o limiar (deve ser ímpar). (f) C: Constante subtraída da média. Copie e Teste! import cv2 from matplotlib import pyplot as plt imgOriginal = cv2.imread("comprimidos.png", 0) # Um filtro de suavização ajuda a reduzir o ruído antes da binarização imgTratada = cv2.medianBlur(imgOriginal, 7) # Parâmetros valorMaximo = 255 tamanhoBloco = 11 constanteC = 5 tipoAdaptacao = cv2.ADAPTIVE_THRESH_GAUSSIAN_C tipoBinarizacao = cv2.THRESH_BINARY_INV imgBinarizada = cv2.adaptiveThreshold(imgTratada, valorMaximo, tipoAdaptacao, tipoBinarizacao, tamanhoBloco, constanteC) figura = plt.figure(figsize=(10, 5)) eixo1 = figura.add_subplot(121) plt.imshow(imgOriginal, cmap='gray') plt.title("Imagem Original") eixo2 = figura.add_subplot(122) plt.imshow(imgBinarizada, cmap='gray') plt.title("Binarização Adaptativa") plt.show() 84 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.52: Ilustração do resultado do processo de binarização adaptativa. Fonte: Adaptado de [2]. 2.2.2 Segmentação por Detecção de Bordas Técnicas de realce de bordas, como o detector de Canny, também podem ser usadas para segmentação. A ideia é encontrar os contornos dos objetos para isolá-los. Um pipeline comum para isso é mostrado abaixo. Figura 2.53: Etapas para realizar segmentação por bordas. Fonte: Elaborado pelos autores. Para este exemplo, vamos usar a seguinte imagem. Figura 2.54: Imagem com grãos de café. Fonte: Adaptado de [2]. 85 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS O código a seguir implementa essas ações em conjunto. Todas as operações já foram abordadas anteriormente. Copie e Teste! import cv2 import numpy as np from matplotlib import pyplot as plt imagem = cv2.imread("graoes-de-cafe.png", 0) # 1. Binarização metodo = cv2.THRESH_BINARY_INV ret, imgBinarizada = cv2.threshold(imagem, 135, 255, metodo) # 2. Morfologia para limpar a imagem binarizada elementoEstruturante = cv2.getStructuringElement(cv2.MORPH_ELLIPSE , (3, 3)) imgTratada = cv2.morphologyEx(imgBinarizada, cv2.MORPH_CLOSE, elementoEstruturante) imgTratada = cv2.erode(imgTratada, elementoEstruturante, iterations=2) # 3. Detector de Bordas Canny imgSegmentada = cv2.Canny(imgTratada, 100, 200) # Exibição dos resultados parciais e final fig, eixos = plt.subplots(1, 4, figsize=(16, 4)) titulos = ['Original','Binarizada','Tratada (Morfologia)',' Segmentada (Canny)'] imagens = [imagem, imgBinarizada, imgTratada, imgSegmentada] for iin range(4): eixos[i].imshow(imagens[i], cmap='gray') eixos[i].set_title(titulos[i]) eixos[i].set_xticks([]), eixos[i].set_yticks([]) plt.tight_layout() plt.show() 86 CAPÍTULO 2. PRÉ-PROCESSAMENTO E SEGMENTAÇÃO DE IMAGENS Saída Esperada Figura 2.55: Ilustração do resultado de segmentação por bordas. Fonte: Adaptado de [2]. Ao analisar as imagens, temos: a imagem original, a imagem binária, a imagem tratada por meio dos processos de fechamento e erosão e, por último, a imagem segmentada que representa nossa região de interesse. Até aqui, vimos os conceitos de pré-processamento e segmentação de imagens. Na seção de pré-processamento, foram apresentados como obter informações de imagens, realizar operações e tratar ruídos. Já na seção de segmentação, apresentamos as principais técnicas, como binarização e segmentação por bordas. No próximo capítulo, vamos abordar como, a partir desses conceitos, podemos extrair características e reconhecer objetos. 87 Capítulo 3 Extração de Características e Reconhecimento de Objetos 3.1 Extração de Características em Visão Computacional A extração de características é o processo que tem como objetivo identificar e quantificar atributos relevantes de uma imagem para descrever seu conteúdo de forma compacta e informativa. Esses atributos, ou descritores, representam objetos, texturas, formas ou padrões, facilitando tarefas como reconhecimento, classificação e segmentação. As características de um objeto podem ser classificadas em categorias, sendo as principais: características de aspecto, dimensionais, inerciais, cores, dentre outras. Características de Cores As cores em uma imagem são representadas em espaços como RGB (vermelho, verde, azul), HSV (matiz, saturação, valor), escala de cinza ou binárias. A extração de características de cor pode incluir histogramas, médias ou desvios padrão dos canais. Para extrair características de cores, podemos usar a função ravel(), que transforma matrizes de pixels em vetores, simplificando cálculos com funções do OpenCV, como cv2.calcHist para histogramas. Figura 3.1: Imagens binária e em tons de cinza para extração de características de cor. Fonte: Imagem adaptada de https://cebb.org.br/henriquelemesembelem/. Veja a figura acima, onde temos duas imagens. A primeira imagem é uma imagem binária e a segunda, uma imagem em níveis de cinza. Usaremos essas imagens para extrair 88 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS Figura 3.6: Resultado da detecção de contornos e cálculo de perímetro. Fonte: Elaborada pelos autores. Caso Prático: Contagem de Objetos Para ilustrar na prática os conceitos de extração de características, vamos aplicar o método findContours junto com o método contourArea para calcular a área de objetos e contar quantos objetos foram encontrados. Para este experimento, vamos usar a imagem abaixo. Figura 3.7: Imagem contendo bolinhas. Fonte: Imagem de Freepik 95 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS Para este caso prático, vamos seguir as etapas descritas abaixo. Caso Prático (1) Passo 1: Ler a imagem com OpenCV. (2) Passo 2: Converter a imagem para escala de cinza. (3) Passo 3: Aplicar filtro GaussianBlur para reduzir ruído. (4) Passo 4: Binarizar a imagem. (5) Passo 5: Encontrar contornos na imagem. (6) Passo 6: Contar os contornos encontrados. O código apresentado abaixo implementa os passos definidos para contar quantos objetos há na imagem. Copie e Teste! import cv2 import numpy as np # Passo 1: Carregar a imagem original imagemOriginal = cv2.imread("bolinhaAmarelas.png") # Passo 2: Converter para tons de cinza imagemCorCinza = cv2.cvtColor(imagemOriginal, cv2.COLOR_BGR2GRAY) # Passo 3: Aplicar filtro GaussianBlur para reduzir ruído imgTratada = cv2.GaussianBlur(imagemCorCinza, (11, 11), 0) # Passo 4: Binarizar a imagem imgBinarizada = cv2.adaptiveThreshold(imgTratada, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 9, 7) # Passo 5: Encontrar contornos na imagem # Usar cv2.RETR_EXTERNAL para pegar apenas os contornos externos ( evita contar contornos dentro de outros) contornos, hierarquia = cv2.findContours(imgBinarizada, cv2. RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contador = 0 # Para cada contorno encontrado for contorno in contornos: # Opcional: filtrar contornos muito pequenos que podem ser ruído if cv2.contourArea(contorno) > 50: # Calcular o retângulo delimitador x, y, w, h = cv2.boundingRect(contorno) 96 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS # Desenhar o retângulo na imagem original cv2.rectangle(imagemOriginal, (x, y), (x + w, y + h), (0, 255, 0), 2) contador += 1 print(f"Foram encontradas {contador} bolinhas.") # Exibir as imagens cv2.imshow("Imagem Original com Retângulos", imagemOriginal) cv2.imshow("Imagem em Tons de Cinza", imagemCorCinza) cv2.imshow("Imagem Binarizada", imgBinarizada) cv2.waitKey(0) cv2.destroyAllWindows() Ao executar o código apresentado, devemos ter uma saída similar à apresentada na imagem abaixo. A variável contador é inicializada com o valor 0 e, para cada contorno encontrado, é somado 1 a ela para registrar quantos objetos foram detectados. Saída Esperada Foram encontradas 200 bolinhas. Figura 3.8: Ilustração do resultado da contagem de objetos. Fonte: Elaborado pelos autores. 3.1.1 Detecção e Reconhecimento de Objetos Iniciando o diálogo... Imagine que você, aluno, tem uma tarefa de analisar imagens para identificar pragas ou doenças em lavouras. Imagine que há dezenas de imagens e plantas diferentes. Mas nem sempre os detalhes vistos a olho nu são suficientes para realizar essas classificações. Como fazer com que os computadores façam esse trabalho por nós usando a Visão Computacional? Anteriormente, vimos que podemos extrair características de objetos e até mesmo detectar objetos como o apresentado na seção anterior, mas observe: detectar objetos pelo seu contorno, ou dizer se um objeto é de uma cor ou outra, é uma tarefa simples, e não foi possível, 97 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS por exemplo, classificar os objetos no sentido de saber qual é o objeto que foi detectado em uma foto ou vídeo. Esta é uma tarefa mais robusta que requer estudos nas áreas de aprendizado de máquina, redes neurais e aprendizado profundo, que, a partir da extração de diversas características, fazem com que o computador possa aprender para determinar e classificar objetos. Mas como exatamente o computador aprende a ver e a classificar? A resposta está nas Redes Neurais Artificiais. O que é uma Rede Neural? Inspiradas na estrutura e funcionamento do cérebro humano, as Redes Neurais são um modelo computacional central do Aprendizado de Máquina (Machine Learning). Elas são formadas por unidades de processamento interconectadas, chamadas de ”neurônios”, que são organizadas em camadas. A primeira camada recebe os dados brutos (como os pixels de uma imagem) e, à medida que esses dados passam pelas camadas seguintes, cada neurônio realiza um pequeno cálculo. A rede ”aprende”ajustando a força das conexões entre esses neurônios durante um processo de treinamento com milhares de exemplos. Ao final, a última camada fornece um resultado, que pode ser a classificação de um objeto (por exemplo: ”isto é uma maçã”ou ”isto é um pulgão”). Redes Neurais Convolucionais (CNNs): A Visão das Máquinas Quando o assunto é analisar imagens, um tipo especial de rede neural se destaca: a Rede Neural Convolucional (CNN), um pilar do Aprendizado Profundo (Deep Learning). A genialidade da CNN está em sua capacidade de extrair características de forma hierárquica, imitando a forma como o córtex visual humano funciona. Em vez de olhar para todos os pixels da imagem de uma vez, a CNN utiliza ”filtros”(também chamados de kernels) que percorrem a imagem em busca de padrões. Nas primeiras camadas, os filtros aprendem a detectar características muito simples e de baixo nível, como arestas, cantos, texturas e gradientes de cor. À medida que a informação avança para as camadas mais profundas, a rede combina essas características simples para formar padrões mais complexos e abstratos. Por exemplo, ela pode aprender que uma combinação de certas linhas e curvas forma o contorno de uma folha, e que um conjunto específico de texturas e cores pode indicar a presença de uma doença fúngica. Essa habilidade de construir um entendimento complexo a partir de elementos simples é o que torna as CNNs tão poderosas para a extração de características robustas, indo muito além da simples contagem de objetos ou segmentação por cor. É exatamente essa arquitetura poderosa que algoritmos como o YOLO e o Haar Cascade utilizam como base para realizar suas tarefas. Eles empregam redes e modelos de aprendizado de máquina para automatizar o processo de extração de milhares de características relevantes de uma imagem, permitindo não apenas detectar a presença de um objeto (segmentação), mas também identificá-lo com alta precisão (classificação). 98 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS Figura 3.9: Exemplo de detecção e classificação de múltiplos objetos em uma imagem. Fonte: https://olhardigital.com.br/2020/11/25/noticias/tecnologias-de-ia-criam-fotos-depessoas-que-nao-existem/ Veja na imagem acima que, a partir de uma foto, foi possível classificar objetos e animais. Muito interessante, não é? Para realizar esse tipo de atividade, iremos usar o algoritmo YOLO (You Only Look Once), que é uma das mais eficientes abordagens modernas para detecção e classificação de objetos em tempo real, baseada em Redes Neurais Convolucionais (CNNs). Algoritmo YOLO O YOLO (You Only Look Once) é um modelo de detecção de objetos baseado em redes neurais convolucionais (CNNs), projetado para processar imagens ou vídeos em tempo real com alta eficiência [10]. Ele utiliza uma arquitetura de rede neural profunda que realiza a detecção e classificação de objetos em uma única passagem, dividindo a imagem em uma grade e prevendo caixas delimitadoras (bounding boxes), classes e probabilidades de confiança para cada célula da grade. Essas caixas são similares aos contornos já apresentados anteriormente. O YOLO é construído sobre CNNs, que extraem características visuais (como bordas, texturas e formas) de imagens. Este algoritmo é amplamente utilizado por sua velocidade, capacidade e precisão. Abaixo temos alguns exemplos onde podemos aplicar esse algoritmo: (a) Veículos Autônomos: para detectar pedestres, ciclistas e sinais de trânsito. (b) Segurança: em tarefas como monitorar câmeras de segurança para detectar atividades suspeitas, como intrusos ou objetos abandonados. (c) Agricultura: em tarefas como monitoramento de plantações para detectar pragas, doenças ou frutas maduras. Reconhecendo Objetos com o YOLO Para iniciar os trabalhos com o YOLO, é bem simples. Bastando fazer a instalação da biblioteca ultralytics. Para isso, execute no terminal o comando abaixo. 99 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS pip install ultralytics Reconhecendo Objetos na Câmera Veja um exemplo abaixo onde usamos a própria WebCam do computador para detectar objetos em tempo real. Copie e Teste! from ultralytics import YOLO import cv2 # Carregar o modelo pré-treinado modelo = YOLO("yolov8n.pt") # Iniciar a captura da webcam cap = cv2.VideoCapture(0) # "0" é a webcam padrão if not cap.isOpened(): print("Erro: Não foi possível abrir a webcam") exit() print("Pressione 'q' para fechar a câmera") while True: # Capturar frame da webcam ret, frame = cap.read() if not ret: print("Erro: Falha ao capturar o frame") break # Fazer detecção no frame results = modelo.predict(source=frame, show=False, conf=0.5) # Desenhar as detecções no frame objetoDetecado = results[0].plot() # Exibir o frame com detecções cv2.imshow("YOLOv8 Webcam Detection", objetoDetecado) # Fechar ao pressionar 'q' if cv2.waitKey(1) & 0xFF == ord('q'): print("Câmera fechada") break # Liberar a webcam e fechar a janela cap.release() cv2.destroyAllWindows() O código acima é bem simples e similar ao apresentado no Capítulo 1, quando aprendemos a ler vídeos da câmera. O que muda aqui são os seguintes pontos: agora estamos importando a classe YOLO com o comando from ultralytics import YOLO, e depois 100 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS carregamos o modelo pré-treinado yolov8n.pt com o comando YOLO("yolov8n.pt"). Este modelo contém características de vários objetos, como pessoas, carros, frutas, dentre outros. A partir desse modelo, os objetos no vídeo poderão ser detectados com a função modelo.predict(...), que recebe três parâmetros: a fonte da imagem (que, neste caso, é o frame), a variável show=False (que desativa a exibição automática de resultados pelo YOLO, pois usaremos o OpenCV para exibir) e conf=0.5 (que determina que o grau de confiança mínimo para uma detecção seja de 50%; objetos com grau de confiança menor serão ignorados). Ao executar o código apresentado, devemos ter uma saída similar à apresentada na imagem abaixo. Saída Esperada Figura 3.10: Ilustração de detecção de objetos com YOLO. Fonte: Elaborado pelos autores. Reconhecendo Objetos em Imagens Veja um exemplo abaixo onde, a partir de uma imagem, o YOLO reconhece e classifica objetos. Copie e Teste! from ultralytics import YOLO import cv2 from matplotlib import pyplot as plt # Carregar o modelo model = YOLO('yolov8n.pt') # Caminho da imagem imagem_path = "cachorros.png" # Fazer a predição results = model.predict(source=imagem_path, conf=0.6) # Carregar a imagem original para desenhar image = cv2.imread(imagem_path) 101 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS # Obter caixas delimitadoras e nomes das classes boxes = results[0].boxes names = model.names # Desenhar as caixas for box in boxes: # Coordenadas da bounding box x1, y1, x2, y2 = map(int, box.xyxy[0]) # Confiança e classe conf = float(box.conf[0]) cls_id = int(box.cls[0]) label = f"{names[cls_id]}: {conf:.2f}" # Desenhar retângulo e texto cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(image, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # Converter de BGR para RGB para exibir com Matplotlib image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Exibir a imagem com as detecções plt.figure(figsize=(10, 6)) plt.imshow(image_rgb) plt.axis('off') plt.title("Objetos detectados com bounding boxes") plt.show() O que está acontecendo aqui? A partir da imagem passada, que neste caso foi o arquivo cachorros.png, o modelo YOLO irá detectar os objetos e, para cada objeto, irá classificá-lo. Veja a saída abaixo. Saída Esperada Figura 3.11: Exemplo de detecção de animais. Fonte: Adaptado de https://fisiocarepet.com.br/quantos-anos-vive-um-cachorro-entenda-as-variaveis/ 102 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS Até agora, vimos que o YOLO é muito eficiente para detectar e classificar objetos a partir de imagens. Mas veja que isso é um pouco genérico, pois os exemplos passados acima disponibilizam códigos que reconhecem qualquer objeto em uma imagem. Imagine que você, aluno, foi contratado por uma empresa para, a partir de imagens, detectar queimadas em uma determinada região da sua cidade. Isso é possível fazer com Visão Computacional usando o YOLO. Observe que, neste cenário, as imagens que nos interessam são apenas imagens de florestas com presença de fumaça ou fogo. Vamos a um caso prático mais detalhado. Caso Prático Neste caso prático, iremos aprender como treinar um modelo no YOLO a partir de uma base de dados contendo imagens com fogo e fumaça, para que depois possamos classificar se uma área está em processo de queimada. Para este exemplo, iremos precisar de uma base de dados com centenas de imagens de queimadas e fumaças para o computador aprender as suas características. Para isso, podemos usar bases de dados públicas disponibilizadas na internet. Neste caso específico, iremos usar a base de dados HPWREN Wildfire Smoke Dataset disponível no Roboflow no link https://public.roboflow.com/object-detection/wildfire-smoke/1/download/yolov8. O Roboflow é uma plataforma que facilita o desenvolvimento de aplicações de Visão Computacional. Ele permite que usuários gerenciem datasets de imagens, anotem objetos, apliquem pré-processamento e treinem modelos de detecção. Figura 3.12: Baixando dados do Roboflow. Fonte: Elaborado pelos autores. Ao baixar e descompactar o arquivo em seu computador, ele deve ter os seguintes arquivos e estrutura: 103 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS Figura 3.13: Arquivos do dataset baixado. Fonte: Elaborado pelos autores. Explicação da Estrutura: (a) train/images: Contém as imagens de treinamento. (b) valid/images: Contém as imagens de validação. (c) train/labels: Contém os arquivos .txt com as anotações para as imagens de treinamento no formato YOLO. (d) valid/labels: Contém os arquivos .txt com as anotações para as imagens de validação. (e) data.yaml: O arquivo de configuração que aponta para essas pastas e define as classes (neste caso, fire e smoke). O que realmente precisamos fazer antes de iniciar nosso código? Primeiro, vamos criar uma pasta para o projeto. Neste exemplo, criamos uma pasta chamada dados. Após criar a pasta, vamos copiar as pastas train,valid e o arquivo data.yaml para dentro da pasta dados. Figura 3.14: Ilustração arquivos baixados. Fonte: Elaborado pelos autores. 104 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS O código acima encontra todas as faces que estiverem na imagem com o comando face_cascade.detectMultiScale(...). E, para cada face encontrada, obtém as coordenadas do contorno para desenhar um retângulo com o comando cv2.rectangle(...). 3.1.4 Aplicando Seus Conhecimentos Esse mesmo exemplo de detecção de faces pode ser aplicado a vídeos. Nesta atividade prática, você deve executar o código abaixo e verificar se o mesmo encontra faces com a WebCam do seu computador. Copie e Teste! import numpy as np import cv2 # Capturar vídeo da webcam cap = cv2.VideoCapture(0) # Carregar o classificador Haar Cascade face_cascade = cv2.CascadeClassifier(' haarcascade_frontalface_default.xml') while(True): # Ler frames retorno, frame = cap.read() if not retorno: print("Erro ao capturar o frame da câmera.") break # Transformar frame em tons de cinza gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Detectar faces faces = face_cascade.detectMultiScale(gray, 1.3, 5) # Listar e desenhar retângulos na face for (x,y,w,h) in faces: # Desenhar retângulos cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,255),2) # Exibir imagem cv2.imshow('Detecção de Rosto',frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() Neste capítulo, aprendemos sobre extração de características de imagens e como pode111 CAPÍTULO 3. EXTRAÇÃO DE CARACTERÍSTICAS E RECONHECIMENTO DE OBJETOS mos usar os algoritmos YOLO eHaar Cascade para detecção e classificação de objetos. No próximo capítulo, iremos apresentar alguns experimentos práticos, aplicando os conhecimentos adquiridos no decorrer deste material. 112 Capítulo 4 Experimentos Práticos Iniciando o diálogo... Neste capítulo, é hora de colocar a mão na massa! Vamos transformar o conhecimento adquirido em código e modelos práticos, aplicando as técnicas de Visão Computacional, junto com OpenCV e o algoritmo YOLO, em cenários para resolver problemas reais. Está pronto para dar o próximo passo e ver a Inteligência Artificial em ação? 4.1 Aplicação 01: Reconhecimento e Classificação de Tomates Caso Prático Neste primeiro experimento prático, vamos demonstrar como a Visão Computacional pode atuar na agricultura. Aqui, o objetivo é, a partir de imagens de tomates, classificá-los como Verdes, Maduros e Podres. 4.1.1 Obtendo dados de imagens referentes aos tomates Para este experimento, vamos disponibilizar o arquivo de modelo do YOLO com as características. O conjunto de dados referente a tomates foi obtido a partir do Roboflow em https://universe.roboflow.com/paulinhofernandes/tomates-4blsk e suas características foram extraídas conforme já apresentado no Capítulo 3. A seguir, iremos demonstrar dois exemplos deste experimento: o primeiro irá detectar tomates em imagens e o segundo, em vídeo. Para este experimento, usamos as duas fotos a seguir. Você pode testar o código com outras imagens, tirando fotos com um celular, usando imagens da internet ou vídeos. O exemplo que será apresentado, além de classificar os tomates, faz a contagem de quantos estão bons, maduros ou estragados. 113 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS Figura 4.1: Imagens de tomates usadas no exemplo. Fontes: https://www.flipkart.com/willvine-ndir-935-ace-55-tomato-seeds-seed/p/itm05903f5fb895b https://pt.rivulis.com/introducao-a-irrigacao-por-gotejamento-subterranea-sdi/. Reconhecimento e classificação a partir de imagens Copie e Teste! import cv2 from ultralytics import YOLO # Carrega o modelo treinado para tomates model = YOLO('dataSet.pt') # Define os rótulos e cores correspondentes às classes do modelo labels = {0: "Estragado", 1: "Bom", 2: "Verde"} colors = {0: (0, 0, 255), 1: (0, 255, 0), 2: (255, 0, 0)} # Vermelho, Verde, Azul # Função para classificar e contar tomates em uma imagem def classify_tomatoes(image_path): # Faz a predição com o modelo results = model.predict(image_path, save=False, imgsz=640, conf=0.6) # Contadores count_bons = 0 count_estragados = 0 count_verdes = 0 # Carrega a imagem para desenho img = cv2.imread(image_path) if img is None: 114 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS print(f"Erro ao carregar a imagem {image_path}!") return None # Processa cada resultado for result in results: boxes = result.boxes for box in boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) class_id = int(box.cls[0]) confidence = float(box.conf[0]) label_text = f"{labels.get(class_id, 'Desconhecido')} {confidence:.2f}" color = colors.get(class_id, (255, 255, 255)) # Desenha o retângulo e o texto cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, label_text, (x1, y1 - 10), cv2. FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # Conta por categoria if class_id == 1: count_bons += 1 elif class_id == 0: count_estragados += 1 elif class_id == 2: count_verdes += 1 # Exibe as contagens no terminal print(f"Tomates Bons (Maduros): {count_bons}") print(f"Tomates Estragados (Podres): {count_estragados}") print(f"Tomates Verdes: {count_verdes}") return img # Caminho da imagem para classificar imagem_para_classificar = "01-B.png" # Classifica a imagem e exibe o resultado resultado_img = classify_tomatoes(imagem_para_classificar) if resultado_img is not None: cv2.imshow("Detector de Tomates", resultado_img) cv2.waitKey(0) cv2.destroyAllWindows() Para a saída apresentada abaixo, foi utilizada a imagem da esquerda, com tomates bons. 115 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS Saída Esperada Tomates Bons (Maduros): 6 Tomates Estragados (Podres): 0 Tomates Verdes: 0 Figura 4.2: Resultado da classificação de tomates bons. Fonte: Adaptado de https://www.flipkart.com/willvine-ndir-935-ace-55-tomato-seedsseed/p/itm05903f5fb895b. O algoritmo foi executado novamente, trocando para a imagem com tomates estragados. A saída teve um resultado conforme apresentado abaixo. Saída Esperada Tomates Bons (Maduros): 0 Tomates Estragados (Podres): 3 Tomates Verdes: 0 116 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS Figura 4.3: Resultado da classificação de tomates estragados. Fonte: Adaptado de https://pt.rivulis.com/introducao-a-irrigacao-por-gotejamento-subterranea-sdi/. Reconhecimento e classificação a partir de vídeos Ainda no experimento 1, podemos aplicar este mesmo conceito para detectar e classificar tomates em vídeos, seja a partir de arquivos ou em tempo real com a WebCam. Veja o mesmo código adaptado para usar a câmera do computador. Copie e Teste! import cv2 from ultralytics import YOLO # Carregar o modelo YOLO treinado model = YOLO('dataSet.pt') # Definir rótulos e cores labels = {0: "Estragado", 1: "Bom", 2: "Verde"} colors = {0: (0, 0, 255), 1: (0, 255, 0), 2: (255, 0, 0)} # Vermelho, Verde, Azul def classify_tomatoes_video(): # Inicializar a webcam (0 = câmera padrão) cap = cv2.VideoCapture(0) if not cap.isOpened(): print("Erro ao abrir a webcam!") return 117 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS # Loop principal (processamento frame a frame) while True: ret, frame = cap.read() if not ret: print("Erro ao capturar frame!") break # Realizar detecção results = model.predict(frame, imgsz=640, conf=0.6) counts = {0: 0, 1: 0, 2: 0} # Estragado, Bom, Verde # Desenhar detecções for result in results: for box in result.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) cls = int(box.cls[0]) conf = float(box.conf[0]) # Atualizar contagem if cls in counts: counts[cls] += 1 # Desenhar caixa e rótulo label = f"{labels.get(cls, 'N/A')} {conf:.2f}" cv2.rectangle(frame, (x1, y1), (x2, y2), colors. get(cls), 2) cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors. get(cls), 2) # Mostrar contagens na tela cv2.putText(frame, f"Bom: {counts[1]}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, colors[1], 2) cv2.putText(frame, f"Estragado: {counts[0]}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, colors[0], 2) cv2.putText(frame, f"Verde: {counts[2]}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, colors[2], 2) # Mostrar frame processado (fora do loop de detecção) cv2.imshow("Detecção de Tomates - Webcam", frame) # Sair ao pressionar 'q' (fora do loop de detecção) if cv2.waitKey(1) & 0xFF == ord('q'): break # Liberar recursos cap.release() cv2.destroyAllWindows() 118 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS # Chamando a função classify_tomatoes_video() A saída apresentada abaixo mostra um exemplo prático. Mas você pode testar com outros cenários. Saída Esperada Figura 4.4: Exemplo de detecção e classificação de tomates por vídeo. Fonte: Elaborado pelos autores. 4.2 Aplicação 02: Detecção de Doenças ou Pragas em Plantas Caso Prático Neste segundo experimento prático, vamos focar em uma das aplicações mais impactantes da Visão Computacional na agricultura: o diagnóstico de doenças em plantas. Utilizaremos o algoritmo YOLO, de forma similar ao que foi usado no experimento anterior dos tomates, para analisar imagens de folhas e determinar se estão saudáveis ou acometidas por alguma doença. 4.2.1 Obtendo dados de imagens referentes a folhas com doenças Para este experimento, vamos disponibilizar o arquivo de modelo do YOLO com as características. O conjunto de dados referente a folhas com doenças foi obtido a partir do site Roboflow em https://universe.roboflow.com/leaf-disease-detection-uurac/leaf-disease-detections7stw e suas características foram extraídas conforme já apresentado no Capítulo 3. A seguir, iremos demonstrar um exemplo prático deste experimento, que tem como objetivo avaliar se uma planta está com alguma praga ou doença, ao analisar suas folhas. O 119 CAPÍTULO 4. EXPERIMENTOS PRÁTICOS exemplo a seguir irá detectar, a partir de uma imagem de folhas de plantas, se esta está com alguma doença ou praga. Veja o código de exemplo abaixo. Copie e Teste! from ultralytics import YOLO import cv2 import numpy as np # Carregar o modelo treinado model = YOLO("dataset_doencas_plantas.pt") # Carregar a imagem image_path = "folhaDoente.jpg" frame = cv2.imread(image_path) if frame is None: print("Erro: Não foi possível carregar a imagem") exit() # Realizar a predição results = model.predict(source=frame, show=False, conf=0.2) # Copiar o frame original para desenhar annotated_frame = frame.copy() # Flag para verificar se alguma doença foi detectada plantaDoente = False for result in results: boxes = result.boxes for box in boxes: # IDs de exemplo para doenças; ajuste conforme seu modelo doencas_ids = ['15','16','17','blight'] if result.names[int(box.cls)] in doencas_ids: plantaDoente = True # Obter coordenadas da caixa delimitadora x1, y1, x2, y2 = map(int, box.xyxy[0]) # Desenhar retângulo vermelho no frame cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), (0, 0, 255), 2) # Adicionar rótulo com o nome da classe cv2.putText(annotated_frame, "Doenca Detectada", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255) , 2) # Após verificar todas as detecções, adiciona o status geral da planta if not plantaDoente: 120