Lab1: Histogram Equalization
1. 實驗環境(C++)
- 操作系統版本 MacOS Catalina 10.15
- OpenCV4.0 (imgcodecs | core | highgui | imgproc)
- Cmake-3.14
- Clang-1100.0.33.8
2. 實驗步驟
-
Calculate the histogram H for src
-
Normalize the histogram.
std::array<double, 256> calNormalizedHist(cv::Mat& source) { std::array<double, 256> acc{0}; // Calculate the histogram H for src for(int i = 0; i < source.rows; i++) for (int j = 0; j < source.cols; j++) acc[ source.ptr<uchar>(i)[j]] ++; // Normalize the histogram. for(int i = 0; i < acc.size(); i++) acc[i] /= source.rows * source.cols; return acc; }
-
Compute the integral of the histogram: H'
-
Transform the image using H′ as a look-up table: $$𝚍𝚜𝚝(x,y)=H′(𝚜𝚛𝚌(x,y)) * 255$$
void equalizeHist(cv::Mat& source, cv::Mat& result) { source.copyTo(result); auto hist = calNormalizedHist(source); // Compute the integral of the histogram: H' for(int i = 1; i < 256; i++) hist[i] += hist[i-1]; // Transform the image using H′ as a look-up table: 𝚍𝚜𝚝(x,y)=H′(𝚜𝚛𝚌(x,y)) * 255 for(int i = 0; i < source.rows; i++) for (int j = 0; j < source.cols; j++) result.ptr<uchar>(i)[j] = hist[source.ptr<uchar>(i)[j]] * 255; }
-
Test Code:
cv::Mat cv, lab, m = cv::imread("../data/lena.png", cv::IMREAD_GRAYSCALE); cv::equalizeHist(m, cv); equalizeHist(m, lab); cv::imwrite("../out/m.png", m); cv::imwrite("../out/cv.png", cv); cv::imwrite("../out/lab.png", lab); cv::imwrite("../out/cv-m.png", abs(cv-m)); cv::imwrite("../out/lab-m.png", abs(lab-m)); cv::imwrite("../out/lab-cv.png", abs(lab-cv)); cv::imwrite("../out/hist.m.png", drawHist(calNormalizedHist(m))); cv::imwrite("../out/hist.cv.png", drawHist(calNormalizedHist(cv))); cv::imwrite("../out/hist.lab.png", drawHist(calNormalizedHist(lab))); cv::imwrite("../out/acchist.m.png", drawHist(calNormalizedHist(m), true)); cv::imwrite("../out/acchist.cv.png", drawHist(calNormalizedHist(cv), true)); cv::imwrite("../out/acchist.lab.png", drawHist(calNormalizedHist(lab),true));
3. 實驗結果

image: Origin

image: OpenCV

image: Result

dif: Origin&OpenCV

dif: Result&Origin

dif: Result&OpenCV

Hist: Origin

Hist: OpenCV

Hist: Result

AccHist: Origin

AccHist: OpenCV

AccHist: Result
-
明顯本文實現的算法與OpenCV實現的高度一致(不考慮指令集優化: SIMD|SEE4 etc.)
-
由Histogram與Accumulate Histogram上看, 連續變量的均勻分布意味着概率分布函數滿足線性分布, 與理論推導得出的性質一致
-
從直方圖直觀觀察和算法分析, 易知基於直接灰度映射(點對點映射)的經典直方圖均衡算法會導致灰階的減少.因而加劇色帶(Banding)現象:
- Lookup-Table \(H’\)在定義上不是單射
所以這個意義上, 直方圖的均衡是一個可以把相鄰柱子合並的挪動柱子的游戲~
5 Attachment
本部分是附加部分, 主要回答實驗課程上與老師的一個小小的 argument:
-
怎么得到均衡后的直方圖 ?
-
比較常見的方法: 圖像均衡后再統計一次直方圖
-
比較少用的方法: 直接均衡原直方圖
std::array<double, 256> equalizeHist(std::array<double, 256>&& hist) { std::array<double, 256> hist_eq{0}, hist_acc{0}; hist_acc[0] = hist[0]; // Compute the integral of the histogram: H' for(int i = 1; i < 256; i++) hist_acc[i] = hist_acc[i-1] + hist[i]; for(int i = 0; i < 256; i++) hist_eq[hist_acc[i]*255] += hist[i]; return hist_eq; }
-
-
先計算均衡后的直方圖再均衡圖像對嗎?
- 不對, 均衡是一種方法, 可以直接均衡圖像然后計算新的直方圖, 也可以直接均衡直方圖, 兩直方圖一致
-
直方圖均衡的均衡指什么?
- 均衡一種概率變換, 可以將線性的目標概率分布函數拓展到任意的分布函數:
Transformation between two distribution funtion.
- 均衡一種概率變換, 可以將線性的目標概率分布函數拓展到任意的分布函數:
5.* 下面給出指定累計直方圖的均衡結果的部分例子
-
\[y=x^2 \]

AccHist

Image

Hist:

Hist: Directly

AccHist

Image

Hist:

Hist: Directly

AccHist

Image

Hist:

Hist: Directly

AccHist

Image

Hist:

Hist: Directly

AccHist

Image

Hist:

Hist: Directly
#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <array>
cv::Mat drawHist(std::array<double, 256>&& acc, bool accumulate = false) {
if(accumulate) for(int i = 1; i < acc.size(); i++) acc[i] += acc[i-1];
double max_v = *std::max_element(acc.begin(), acc.end());
cv::Mat visual = cv::Mat::zeros(256, 256, CV_8UC3);
for(int i = 0; i < 256; i++)
cv::line(visual, cv::Point(i, 256.0*(1-acc[i]/max_v)), cv::Point(i, 256), 255);
return visual;
}
std::array<double, 256> calNormalizedHist(cv::Mat& source) {
std::array<double, 256> acc{0};
// Calculate the histogram H for src
for(int i = 0; i < source.rows; i++)
for (int j = 0; j < source.cols; j++)
acc[ source.ptr<uchar>(i)[j]] ++;
// Normalize the histogram.
for(int i = 0; i < acc.size(); i++)
acc[i] /= source.rows * source.cols;
return acc;
}
std::array<double, 256> equalizeHist(std::array<double, 256>&& hist,
std::function<double(double)> && f=[](double x){return x;}) {
std::array<double, 256> hist_eq{0};
std::array<int, 256> H{0};
double sum = 0;
for(int i = 0, j = 0; i < 256; i++)
for(;sum < f(i/255.0); j++) {
sum += hist[j]; H[j] = i;
}
for(int i = 0; i < 256; i++)
hist_eq[H[i]] += hist[i];
return hist_eq;
}
// Only for single channel
void equalizeHist(cv::Mat& source, cv::Mat& result,
std::function<double(double)> && f=[](double x){return x;}) {
source.copyTo(result);
auto hist = calNormalizedHist(source);
std::array<int, 256> H{0};
// Compute the integral of the histogram: H'
double sum = 0;
for(int i = 0, j = 0; i < 256; i++)
for(;sum < f(i/255.0); j++) {
sum += hist[j]; H[j] = i;
}
// directly gray mapping
// Transform the image using H′ as a look-up table: 𝚍𝚜𝚝(x,y)=H′(𝚜𝚛𝚌(x,y)) * 255
for(int i = 0; i < source.rows; i++)
for (int j = 0; j < source.cols; j++)
result.ptr<uchar>(i)[j] = H[source.ptr<uchar>(i)[j]];
}
int main() {
cv::Mat m = cv::imread("../data/lena.bmp", cv::IMREAD_GRAYSCALE);
cv::Mat cv, lab;
cv::equalizeHist(m, cv);
equalizeHist(m, lab);
cv::imwrite("../out/m.bmp", m);
cv::imwrite("../out/cv.bmp", cv);
cv::imwrite("../out/lab.bmp", lab);
cv::imwrite("../out/cv-m.bmp", abs(cv-m));
cv::imwrite("../out/lab-m.bmp", abs(lab-m));
cv::imwrite("../out/lab-cv.bmp", abs(lab-cv));
cv::imwrite("../out/hist.m.bmp", drawHist(calNormalizedHist(m)));
cv::imwrite("../out/hist.cv.bmp", drawHist(calNormalizedHist(cv)));
cv::imwrite("../out/hist.lab.bmp", drawHist(calNormalizedHist(lab)));
cv::imwrite("../out/hist.lab_direct.bmp", drawHist(equalizeHist(calNormalizedHist(m))));
cv::imwrite("../out/acchist.m.bmp", drawHist(calNormalizedHist(m), true));
cv::imwrite("../out/acchist.cv.bmp", drawHist(calNormalizedHist(cv), true));
cv::imwrite("../out/acchist.lab.bmp", drawHist(calNormalizedHist(lab),true));
{
auto f = [&](std::string f_name, std::function<double(double)>&& f) {
cv::Mat m_f;
equalizeHist(m, m_f, std::move(f));
cv::imwrite("../out/lab."+f_name+".bmp", m_f);
cv::imwrite("../out/hist."+f_name+".bmp", drawHist(calNormalizedHist(m_f)));
cv::imwrite("../out/acchist."+f_name+".bmp", drawHist(calNormalizedHist(m_f), true));
cv::imwrite("../out/hist."+f_name+"_direct.bmp",drawHist(equalizeHist(calNormalizedHist(m), std::move(f)))) ;
};
f("x^2", [](double x){return x*x;});
f("sin(x)", [](double x){return sin(3.1415926 * 0.5 * x);});
f("sqrt(x*(2-x))", [](double x){return sqrt(x*(2-x));});
f("exp(x-1)", [](double x){return std::exp(x-1);});
f("x^5", [](double x){return std::pow(x,5);});
}
// cv::imshow("lena", m);
// cv::imshow("lena: equalizeHist", cv);
// cv::imshow("lena: equalizeHist zlb", lab);
//
//
// cv::imshow("origin", drawHist(calNormalizedHist(m)));
// cv::imshow("cvHist", drawHist(calNormalizedHist(cv), true));
// cv::imshow("zlbHist", drawHist(calNormalizedHist(lab), true));
// cv::waitKey();
return 0;
}