๐Ÿฆ„AI/Computer Vision

[Computer Vision/OpenCV] 9. Histogram Matching

mingyung 2025. 3. 27. 01:13

 

 

์ €๋ฒˆ ํฌ์ŠคํŒ…์œผ๋กœ ํžˆ์Šคํ† ๊ทธ๋žจ Equalization์„ ๋ฐฐ์› ๋‹ค. Histogram Equalization์€ ํžˆ์Šคํ† ๊ทธ๋žจ์˜ ๋ถ„ํฌ๋ฅผ ๊ท ์ผํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

์˜ค๋Š˜์€, ๊ท ์ผํ•œ ๋ถ„ํฌ๋ฅผ๋„˜์–ด์„œ์„œ  "ํŠน์ •ํ•œ ๋ถ„ํฌ"๋กœ ๋ฐ”๊พธ๋Š” Histogram Matching์— ๋Œ€ํ•ด์„œ ๋ฐฐ์šด๋‹ค.

์ด ๊ณผ์ •์—์„œ Histogram Equalization์ด ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— equalization์— ๋Œ€ํ•ด์„œ ์–ด๋А์ •๋„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

Histogram Matching

ํŠน์ •ํ•œ ํžˆ์Šคํ† ๊ทธ๋žจ ํ˜•ํƒœ๋กœ ์ด๋ฏธ์ง€์˜ ์›๋ณธ ํžˆ์Šคํ† ๊ทธ๋žจ์„ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ histogram matching์ด๋ผ๊ณ  ํ•œ๋‹ค.

์ฆ‰, ์›๋ณธ ํžˆ์Šคํ† ๊ทธ๋žจ S๋ฅผ ๋ณ€ํ˜•ํ•˜์—ฌ ํžˆ์Šคํ† ๊ทธ๋žจ Z๋กœ ๋งŒ๋“ ๋‹ค.

 

 

Histogram Matching ๊ณผ์ •

์œ„์˜ ์‚ฌ์ง„์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด Histogram Equalization์„ ํ†ตํ•ด์„œ Transfer Function T์™€ G๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ตฌํ•œ ๋‘๊ฐœ์˜ Transfer Funciton์„ ํ™œ์šฉํ•˜๋ฉด S->Z๋กœ ํžˆ์Šคํ† ๊ทธ๋žจ ๋งค์นญ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

1. ๋จผ์ € ์›๋ณธ๊ณผ ๋ชฉํ‘œ ํžˆ์Šคํ† ๊ทธ๋žจ์˜ equalization function์„ ๊ตฌํ•œ๋‹ค.

2. ์ด ๋‘ function์„ ํ™œ์šฉํ•ด s->z๋กœ intensity ๋งคํ•‘์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. 

z=Gโˆ’1(d)=Gโˆ’1(T(s))

 

Histogram Matching OpenCV ์‹ค์Šต

์ปฌ๋Ÿฌ์™€ ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ์ด๋ฏธ์ง€ ๋ชจ๋‘์—์„œ ์ ์šฉํ•œ ์˜ˆ์‹œ๋ฅผ ๋“ค๊ณ  ์™”๋‹ค.

์ปฌ๋Ÿฌ ์ด๋ฏธ์ง€์˜ ๊ฒฝ์šฐ์—๋Š” YUV๋ณ€ํ™˜ ํ•˜์—ฌ Y(๋ฐ๊ธฐ)์—์„œ๋จ„ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

(๋”ฐ๋ผ์„œ ๋ฐ๊ธฐ๋งŒ ๋ณ€ํ•˜๋ฏ€๋กœ, ํžˆ์Šคํ† ๊ทธ๋žจ์€ grayscale๊ณผ ๋™์ผํ•  ๊ฒƒ์ด๋‹ค)

 

Grayscale Image

๊ฐ€์žฅ ์œ„์˜ ์‚ฌ์ง„๊ณผ ํžˆ์Šคํ† ๊ทธ๋žจ์ด ๋ณ€ํ™˜ํ•  ์‚ฌ์ง„

์ค‘๊ฐ„ ํžˆ์Šคํ† ๊ทธ๋žจ๊ณผ ์‚ฌ์ง„์ด ๋งค์นญ ๋Œ€์ƒ์ด๋‹ค.

๊ฐ€์žฅ ์•„๋ž˜์˜์˜ ์‚ฌ์ง„๊ณผ ํžˆ์Šคํ† ๊ทธ๋žจ์ด ๋งค์นญ ํ›„์˜ ์‚ฌ์ง„๊ณผ ํžˆ์Šคํ† ๊ทธ๋žจ์ด๋‹ค.

 

๊ฒฐ๊ณผ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ํžˆ์Šคํ† ๊ทธ๋žจ ๋ชจ์–‘์ด ์œ ์‚ฌํ•˜๊ฒŒ ๋งค์นญ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค

 

 

Color Image

์œ„์˜ ์‚ฌ์ง„๊ณผ ํžˆ์Šคํ† ๊ทธ๋žจ์ด ๋ณ€ํ™˜ํ•  ์‚ฌ์ง„

๋‘๋ฒˆ์งธ ํžˆ์Šคํ† ๊ทธ๋žจ๊ณผ ์‚ฌ์ง„์ด ๋งค์นญ ๋Œ€์ƒ์ด๋‹ค.

 

์ปฌ๋Ÿฌ ์ด๋ฏธ์ง€๋Š” BGR์ฑ„๋„์„ YUV ์ฑ„๋„๋กœ ๋ฐ”๊พธ์–ด Y์ฑ„๋„์—๋งŒ ์ ์šฉํ•œ๋‹ค.

Y๋Š” ์ด๋ฏธ์ง€์˜ ๋ฐ๊ธฐ๋ฅผ ์˜๋ฏธํ•˜๊ณ , U,V๋Š” ์ด๋ฏธ์ง€ ํ”ฝ์…€์˜ ์ƒ‰์ƒ์„ ๊ฒฐ์ •ํ•œ๋‹ค.

 

ํžˆ์Šคํ† ๊ทธ๋žจ ๋งค์นญ์˜ ๊ฒฐ๊ณผ๋Š” ๋”ฐ๋ผ์„œ ์ด๋ฏธ์ง€์˜ ์ƒ‰์ƒ์ •๋ณด๋Š” ๊ทธ๋Œ€๋กœ, ๋ฐ๊ธฐ ์ •๋ณด๋งŒ ๋งค์นญ๋œ๋‹ค.

 

 

์•„๋ž˜์˜ ๋งค์นญ ๊ฒฐ๊ณผ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ํžˆ์Šคํ† ๊ทธ๋žจ ๋ชจ์–‘์ด ์œ ์‚ฌํ•˜๊ฒŒ ๋งค์นญ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

 

Histogram Matching OpenCV Code

์‚ฌ์šฉ ํ•จ์ˆ˜๊ฐ€ ๋งŽ์•„์ ธ์„œ ๋”ฐ๋กœ ํ—ค๋”ํŒŒ์ผ์„ ๋งŒ๋“ค์—ˆ๋‹ค. 

 

histogram.h

#pragma once
#ifndef HISTOGRAM_H
#define HISTOGRAM_H

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ๋กœ ๋ณ€ํ™˜
inline Mat convertToGrayscale(const Mat& img) {
    Mat grayImg;
    if (img.channels() == 3)
        cvtColor(img, grayImg, COLOR_BGR2GRAY);
    else
        grayImg = img;
    return grayImg;
}


// ํžˆ์Šคํ† ๊ทธ๋žจ ๊ณ„์‚ฐ
inline vector<Mat> calculateHistogram(const Mat& img) {
    vector<Mat> histograms;

    if (img.channels() == 1) {  // Grayscale Image
        Mat hist;
        int histSize = 256;
        float range[] = { 0, 256 };
        const float* histRange = { range };

        calcHist(&img, 1, 0, Mat(), hist, 1, &histSize, &histRange);
        histograms.push_back(hist);
    }
    else if (img.channels() == 3) {  // Color Image (BGR or RGB)
        vector<Mat> channels;
        split(img, channels);

        int histSize = 256;
        float range[] = { 0, 256 };
        const float* histRange = { range };

        for (int i = 0; i < 3; ++i) {
            Mat hist;
            calcHist(&channels[i], 1, 0, Mat(), hist, 1, &histSize, &histRange);
            histograms.push_back(hist);
        }
    }

    return histograms;
}

// ํ™•๋ฅ  ๋ฐ€๋„ ํ•จ์ˆ˜(PDF) ๊ณ„์‚ฐ
inline Mat computePDF(const Mat& hist) {
    return hist / sum(hist)[0];
}

// ๋ˆ„์  ๋ถ„ํฌ ํ•จ์ˆ˜(CDF) ๊ณ„์‚ฐ
inline Mat computeCDF(const Mat& pdf) {
    Mat cdf = pdf.clone();
    for (int i = 1; i < cdf.rows; ++i) {
        cdf.at<float>(i) += cdf.at<float>(i - 1);
    }
    return cdf;
}

// ๋งค์นญ์„ ์œ„ํ•œ Lookup Table ์ƒ์„ฑ
inline Mat generateLookupTable(const Mat& sourceCDF, const Mat& referenceCDF) {
    Mat lookupTable(1, 256, CV_8U);
    for (int i = 0; i < 256; ++i) {
        float sourceValue = sourceCDF.at<float>(i);
        uchar matchedValue = 0;
        for (int j = 0; j < 256; ++j) {
            if (referenceCDF.at<float>(j) >= sourceValue) {
                matchedValue = static_cast<uchar>(j);
                break;
            }
        }
        lookupTable.at<uchar>(i) = matchedValue;
    }
    return lookupTable;
}

// Lookup Table ์ ์šฉ
inline Mat applyLookupTable(const Mat& img, const Mat& lookupTable) {
    Mat result;
    LUT(img, lookupTable, result);
    return result;
}

// ํžˆ์Šคํ† ๊ทธ๋žจ ๋งค์นญinline Mat histogramMatching(
inline Mat histogramMatching(
    const Mat& src,
    const Mat& ref,
    vector<Mat>& hist_src,
    vector<Mat>& hist_dst,
    vector<Mat>& hist_result,
    bool Color = false)
{
    if (src.channels() == 3 && Color) {  // ์ปฌ๋Ÿฌ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (YUV ๋ณ€ํ™˜ ํ›„ Y ์ฑ„๋„ ๋งค์นญ)
        Mat srcYUV, refYUV;
        cvtColor(src, srcYUV, COLOR_BGR2YUV);
        cvtColor(ref, refYUV, COLOR_BGR2YUV);

        // ์ฑ„๋„ ๋ถ„๋ฆฌ
        vector<Mat> srcChannels, refChannels;
        split(srcYUV, srcChannels);
        split(refYUV, refChannels);

        // Y ์ฑ„๋„์˜ ํžˆ์Šคํ† ๊ทธ๋žจ ๊ณ„์‚ฐ
        hist_src = calculateHistogram(srcChannels[0]);
        hist_dst = calculateHistogram(refChannels[0]);

        // PDF ๋ฐ CDF ๊ณ„์‚ฐ
        Mat pdf_src = computePDF(hist_src[0]);
        Mat pdf_dst = computePDF(hist_dst[0]);

        Mat cdf_src = computeCDF(pdf_src);
        Mat cdf_dst = computeCDF(pdf_dst);

        // Lookup Table ์ƒ์„ฑ ๋ฐ ์ ์šฉ
        Mat lookupTable = generateLookupTable(cdf_src, cdf_dst);
        srcChannels[0] = applyLookupTable(srcChannels[0], lookupTable);

        // ๋งค์นญ๋œ ์ด๋ฏธ์ง€ ๋ณ‘ํ•ฉ ๋ฐ ์ƒ‰์ƒ ๋ณต์›
        Mat matchedYUV, result;
        merge(srcChannels, matchedYUV);
        cvtColor(matchedYUV, result, COLOR_YUV2BGR);

        // ๋งค์นญ๋œ ์ด๋ฏธ์ง€์˜ Y ์ฑ„๋„ ํžˆ์Šคํ† ๊ทธ๋žจ ๊ณ„์‚ฐ
        hist_result = calculateHistogram(srcChannels[0]);

        return result;
    }
    else {  // ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
        Mat srcGray = convertToGrayscale(src);
        Mat refGray = convertToGrayscale(ref);

        hist_src = calculateHistogram(srcGray);
        hist_dst = calculateHistogram(refGray);

        Mat pdf_src = computePDF(hist_src[0]);
        Mat pdf_dst = computePDF(hist_dst[0]);

        Mat cdf_src = computeCDF(pdf_src);
        Mat cdf_dst = computeCDF(pdf_dst);

        Mat lookupTable = generateLookupTable(cdf_src, cdf_dst);

        Mat result = applyLookupTable(srcGray, lookupTable);

        hist_result = calculateHistogram(result);

        return result;
    }
}


// ํžˆ์Šคํ† ๊ทธ๋žจ ์ด๋ฏธ์ง€๋กœ ๊ทธ๋ฆฌ๊ธฐ
inline Mat drawHistogram(
    const vector<Mat>& hists,  // ํžˆ์Šคํ† ๊ทธ๋žจ ๋ชฉ๋ก (Y, BGR or YUV)
    int histSize = 256,
    int hist_w = 512,
    int hist_h = 500,
    int margin = 60) {

    Mat histImg(hist_h + 2 * margin, hist_w + 2 * margin, CV_8UC3, Scalar(0, 0, 0));
    Scalar colors[3] = { Scalar(255, 255, 255), Scalar(0, 255, 0), Scalar(0, 0, 255) };  // B, G, R or Y, U, V
    int channelCount = hists.size();

    for (int ch = 0; ch < channelCount; ++ch) {
        Mat hist = hists[ch];

        // ์ •๊ทœํ™”ํ•˜์—ฌ ์ตœ๋Œ€๊ฐ’์„ hist_h์— ๋งž์ถค
        normalize(hist, hist, 0, hist_h, NORM_MINMAX);

        for (int i = 1; i < histSize; ++i) {
            line(histImg,
                Point(margin + (i - 1) * (hist_w / histSize), margin + hist_h - cvRound(hist.at<float>(i - 1))),
                Point(margin + i * (hist_w / histSize), margin + hist_h - cvRound(hist.at<float>(i))),
                colors[ch % 3], 1);  // ์„ ์œผ๋กœ ํ‘œ์‹œ
        }
    }

    // ์ถ• ๊ทธ๋ฆฌ๊ธฐ
    line(histImg, Point(margin - 2, margin), Point(margin - 2, margin + hist_h+2), Scalar(255, 255, 255)); // Y์ถ•
    line(histImg, Point(margin - 2, margin + hist_h+2), Point(margin + hist_w, margin + hist_h+2), Scalar(255, 255, 255)); // X์ถ•

    return histImg;
}

#endif // HISTOGRAM_H

 

hist_matching.cpp

#include "histogram.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat img_src = imread("sunset.jpg", IMREAD_COLOR);
    Mat img_dst = imread("lake.jpg", IMREAD_COLOR);

    if (img_src.empty() || img_dst.empty()) {
        cout << "Error loading images!" << endl;
        return -1;
    }

    vector<Mat> hist_src, hist_dst, hist_result;
    vector<Mat> hist_src_g, hist_dst_g, hist_result_g;

    imshow("Source Image", img_src);
	hist_src = calculateHistogram(img_src);
    // For Grayscale Matching
    Mat result_gray = histogramMatching(img_src, img_dst, hist_src_g, hist_dst_g, hist_result_g, false);

    imshow("Source Image", convertToGrayscale(img_src));
    imshow("Reference Image", convertToGrayscale(img_dst));
    imshow("Matched Image (Grayscale)", result_gray);

    imshow("Source Histogram (Grayscale)", drawHistogram(hist_src_g));
    imshow("Reference Histogram (Grayscale)", drawHistogram(hist_dst_g));
    imshow("Matched Histogram (Grayscale)", drawHistogram(hist_result_g));

    // For Color Matching
    Mat result_color = histogramMatching(img_src, img_dst, hist_src, hist_dst, hist_result, true);

    imshow("Source Image (Color)", img_src);
    imshow("Reference Image (Color)", img_dst);
    imshow("Matched Image (Color)", result_color);

    imshow("Source Histogram (Y Channel)", drawHistogram(hist_src));
    imshow("Reference Histogram (Y Channel)", drawHistogram(hist_dst));
    imshow("Matched Histogram (Y Channel)", drawHistogram(hist_result));

    waitKey(0);
    return 0;
}