Cálculo, Exibição e Aplicação de Histogramas de Cor no OpenCV

O histograma de cores é uma representação fundamental da distribuição de cores em uma imagem. Este guia aborda operações essenciais com histogramas no OpenCV, incluindo cálculo, visualização, comparação e projeção inversa.

Fundamentos: Histogramas com Pesos

Um histograma é uma forma não paramétrica de expressar a distribuição de probabilidade. Para N pontos de dados quantizados em um intervalo de 1 a M, o cálculo envolve uma etapa de "votação" onde cada amostra incrementa o bin correspondente. Para representar uma distribuição de probabilidade, os valores do histograma devem ser normalizados para somar 1.

Um refinamento importante é atribuir pesos diferentes às amostras. Atribuir um peso w[i] = 1/N durante a votação permite que o array resultante contenha diretamente as probabilidades. Modificar os pesos de amostras específicas (por exemplo, para aquelas que representam casos importantes) altera efetivamente a distribuição estimada. Em aplicações como detecção facial, onde o número de amostras é limitado, aumentar o peso de amostras-chave pode simular um aumento na sua quantidade, melhorando a representatividade do modelo.

Operações com Histogramas no OpenCV

O OpenCV fornece a função cv::calcHist para calcular histogramas. Para imagens multicanal (como RGB ou HSV), a imagem deve ser dividida em canais separados usando cv::split antes do cálculo. A estrutura cv::MatND é tipicamente usada para armazenar o resultado.

Exemplo 1: Histograma de Hue-Saturation (HS)

O código a seguir calcula e exibe um histograma 2D para os canais Hue e Saturation de uma imagem HSV.

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

void mostrar_histograma_HS() {
    cv::Mat imagem_orig = cv::imread("flor.jpg");
    if (imagem_orig.empty()) {
        std::cerr << "Falha ao carregar imagem." << std::endl;
        return;
    }

    cv::Mat imagem_hsv;
    cv::cvtColor(imagem_orig, imagem_hsv, cv::COLOR_BGR2HSV);

    std::vector<cv::Mat> canais;
    cv::split(imagem_hsv, canais);

    // Definição dos parâmetros do histograma
    int divisoes_matiz = 30;
    int divisoes_saturacao = 32;
    int tamanho_hist[] = {divisoes_matiz, divisoes_saturacao};
    float faixa_matiz[] = {0, 180};
    float faixa_saturacao[] = {0, 255};
    const float* faixas[] = {faixa_matiz, faixa_saturacao};

    cv::MatND hist;
    cv::calcHist(&canais[0], 1, nullptr, cv::Mat(), hist, 2, tamanho_hist, faixas);
    cv::normalize(hist, hist, 1.0, 0, cv::NORM_L1);

    double max_valor;
    cv::minMaxLoc(hist, nullptr, &max_valor);

    int escala = 10;
    int altura_img = 240;
    int largura_img = divisoes_matiz * divisoes_saturacao * 6;
    cv::Mat imagem_hist(altura_img, largura_img, CV_8UC3, cv::Scalar::all(0));

    int largura_bin = largura_img / (divisoes_matiz * divisoes_saturacao);
    for (int m = 0; m < divisoes_matiz; ++m) {
        for (int s = 0; s < divisoes_saturacao; ++s) {
            float valor_bin = hist.at<float>(m, s);
            int intensidade = cvRound(valor_bin * altura_img / max_valor);

            cv::Mat cor_hsv(1, 1, CV_8UC3, cv::Scalar(m * 180.f / divisoes_matiz,
                                                      s * 255.f / divisoes_saturacao, 255));
            cv::Mat cor_bgr;
            cv::cvtColor(cor_hsv, cor_bgr, cv::COLOR_HSV2BGR);

            int idx = m * divisoes_saturacao + s;
            cv::rectangle(imagem_hist,
                          cv::Point(idx * largura_bin, altura_img),
                          cv::Point((idx + 1) * largura_bin, altura_img - intensidade),
                          cv::Scalar(cor_bgr.at<cv::Vec3b>(0, 0)), -1);
        }
    }

    cv::imshow("Original", imagem_orig);
    cv::imshow("Histograma H-S", imagem_hist);
    cv::waitKey(0);
}

Exemplo 2: Histogramas para Canais RGB Individuais

Este exemplo calcula histogramas separados para os canais Vermelho (R), Verde (G) e Azul (B).

void mostrar_histograma_RGB() {
    cv::Mat imagem_orig = cv::imread("flor.jpg");
    if (imagem_orig.empty()) return;

    std::vector<cv::Mat> canais_bgr;
    cv::split(imagem_orig, canais_bgr);

    int num_bins = 100;
    int faixa_valores[] = {0, 256};
    const float* faixas[] = { (float*)faixa_valores };
    cv::MatND hist_r, hist_g, hist_b;

    cv::calcHist(&canais_bgr[2], 1, 0, cv::Mat(), hist_r, 1, &num_bins, faixas);
    cv::calcHist(&canais_bgr[1], 1, 0, cv::Mat(), hist_g, 1, &num_bins, faixas);
    cv::calcHist(&canais_bgr[0], 1, 0, cv::Mat(), hist_b, 1, &num_bins, faixas);

    cv::normalize(hist_r, hist_r, 1.0, 0, cv::NORM_L1);
    cv::normalize(hist_g, hist_g, 1.0, 0, cv::NORM_L1);
    cv::normalize(hist_b, hist_b, 1.0, 0, cv::NORM_L1);

    double max_r, max_g, max_b;
    cv::minMaxLoc(hist_r, nullptr, &max_r);
    cv::minMaxLoc(hist_g, nullptr, &max_g);
    cv::minMaxLoc(hist_b, nullptr, &max_b);

    int altura_bin = 100;
    int escala = 2;
    cv::Mat imagem_grafico(altura_bin * 3, num_bins * escala, CV_8UC3, cv::Scalar::all(0));

    for (int i = 0; i < num_bins; ++i) {
        float val_r = hist_r.at<float>(i);
        int h_r = cvRound(val_r * altura_bin / max_r);
        cv::rectangle(imagem_grafico,
                      cv::Point(i * escala, altura_bin - 1),
                      cv::Point((i + 1) * escala - 1, altura_bin - h_r),
                      cv::Scalar(255, 0, 0), -1);

        float val_g = hist_g.at<float>(i);
        int h_g = cvRound(val_g * altura_bin / max_g);
        cv::rectangle(imagem_grafico,
                      cv::Point(i * escala, 2 * altura_bin - 1),
                      cv::Point((i + 1) * escala - 1, 2 * altura_bin - h_g),
                      cv::Scalar(0, 255, 0), -1);

        float val_b = hist_b.at<float>(i);
        int h_b = cvRound(val_b * altura_bin / max_b);
        cv::rectangle(imagem_grafico,
                      cv::Point(i * escala, 3 * altura_bin - 1),
                      cv::Point((i + 1) * escala - 1, 3 * altura_bin - h_b),
                      cv::Scalar(0, 0, 255), -1);
    }

    cv::imshow("Original", imagem_orig);
    cv::imshow("Histograma RGB", imagem_grafico);
    cv::waitKey(0);
}

Comparação de Histogramas

O OpenCV oferece diversos métodos para comparar dois histogramas usando cv::compareHist. A interpretação do resultado depende do método escolhido:

  • Correlação (CV_COMP_CORREL) e Itnersecção (CV_COMP_INTERSECT): Valores mais altos indicam maior semelhança.
  • Qui-quadrado (CV_COMP_CHISQR), Bhattacharyya (CV_COMP_BHATTACHARYYA) e Distância da Terra (EMD): Valores mais baixos indicam maior semelhança.

O método EMD (Earth Mover's Distance) é particularmente poderoso, pois mede o "trabalho" necessário para transformar um histograma no outro, sendo robusto a mudanças na intensidade. No entanto, requer que os histogramas sejam convertidos para o formato de assinatura (uma matriz de características) e pode consumir muita memória.

Projeção Inversa de Histograma (Back-Projection)

A projeção inversa é uma técnica para localizar objetos em uma imagem com base em sua distribuição de cor. Ela usa um histograma de referência (do objeto de interesse) para criar um mapa de probabilidade da imagem de busca.

Processo:

  1. Cálculo do histograma de referência a partir de uma imagem de amostra do objeto.
  2. Para cada pixel da imagem de busca, calcula-se a probabilidade de sua cor pertencer ao histograma de referência.
  3. O resultado é uma imagem em escala de cinza onde pixels mais claros correspondem a áreas com maior probabilidade de conter o objeto.

Isso é implementado no OpenCV com a função cv::calcBackProject.

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

int main() {
    cv::Mat amostra = cv::imread("amostra_cor.jpg");
    cv::Mat imagem_busca = cv::imread("cena.jpg");

    // Converter para HSV e calcular histograma da amostra
    cv::Mat amostra_hsv;
    cv::cvtColor(amostra, amostra_hsv, cv::COLOR_BGR2HSV);
    int divisoes[] = {30, 32};
    float faixas_h[] = {0, 180}, faixas_s[] = {0, 255};
    const float* faixas[] = {faixas_h, faixas_s};
    cv::MatND hist_ref;

    std::vector<cv::Mat> canais_amostra;
    cv::split(amostra_hsv, canais_amostra);
    cv::calcHist(&canais_amostra[0], 1, nullptr, cv::Mat(), hist_ref, 2, divisoes, faixas);
    cv::normalize(hist_ref, hist_ref, 1.0, 0, cv::NORM_L1);

    // Aplicar back-projection na imagem de busca
    cv::Mat imagem_busca_hsv;
    cv::cvtColor(imagem_busca, imagem_busca_hsv, cv::COLOR_BGR2HSV);
    std::vector<cv::Mat> canais_busca;
    cv::split(imagem_busca_hsv, canais_busca);

    cv::Mat resultado;
    cv::calcBackProject(&canais_busca[0], 1, nullptr, hist_ref, resultado, faixas);

    cv::imshow("Amostra", amostra);
    cv::imshow("Imagem de Busca", imagem_busca);
    cv::imshow("Projeção Inversa (Probabilidade)", resultado);
    cv::waitKey(0);

    return 0;
}

Correspondência de Padrões (Template Matching)

A correspondência de padrões opera deslizando uma imagem modelo (template) sobre a imagem de busca e medindo a similaridade em cada posição. Diferentemente da projeção inversa, ela compara os valores dos pixels diretamente, não as distribuições de cor.

O OpenCV fornece vários métodos de comparação através de cv::matchTemplate, como diferença quadrática, correlação e coeficiente de correlação. O resultado é um mapa onde cada ponto representa a pontuação de similaridade para a posição central do modelo naquele ponto da imagem de busca.

Tags: OpenCV Histograma de Cores Visão Computacional processamento de imagens Projeção Inversa

Publicado em 6-18 07:48