对图像进行直方图匹配操作,并附C++代码和Python代码
虽然在博文 https://www.hhai.cc/thread-218-1-1.html 中介绍的直方图均衡化函数equalizeHist()可以自动改变直方图的分布形式,但是该函数不能指定均衡化后的直方图分布形式。
在某些特定的条件下,需要将直方图映射成指定的分布形式,这种将直方图映射成指定分布形式的算法称为直方图匹配或直方图规定化。
在实际操作中,通常是把一幅图像的直方图匹配为另一幅图像的直方图。直方图匹配操作可以使得源图像和目标图像的直方图相似或相同。
直方图匹配变换的思路如下:
1 分别求解源图片和目标图片累积直方图概率分布;
2 根据累积直方图概率分布建立直方图匹配变换映射;
3 根据直方图匹配变换映射对原图进行直方图匹配操作。
OpenCV并没有提供直方图匹配的函数,需要自己根据算法写代码实现直方图匹配。
以下给出实现直方图匹配的C++代码和Python代码。
代码中用到的图像下载链接:
https://pan.baidu.com/s/18YDKAtvHZK4eJVZJexhiEA?pwd=vf2y
实现直方图匹配的C++代码如下:
[C++] 纯文本查看 复制代码 //出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
cv::Mat ycMatchHist(cv::Mat srcImg, cv::Mat dstImg)
{
// ****** 如果是 RGB 图片则转为灰度图片操作 ******
Mat out(srcImg);
int graylevel = 256;
int grayArr[256];
int srcRow = srcImg.rows;
int srcCol = srcImg.cols;
int dstRow = dstImg.rows;
int dstCol = dstImg.cols;
float srcCdfArr[256] = { 0.f };
float dstCdfArr[256] = { 0.f };
float tmp;
// *** 求解源图片的累积直方图(概率)分布 ***
memset(grayArr, 0, sizeof(grayArr));
for (size_t nrow = 0; nrow < srcRow; nrow++)
for (size_t ncol = 0; ncol < srcCol; ncol++)
{
int tag = srcImg.at<uchar>(nrow, ncol);
grayArr[tag]++;
}
tmp = 0;
for (int i = 0; i<graylevel; i++)
{
tmp += grayArr[i];
srcCdfArr[i] = tmp / (srcRow * srcCol);
// std::cout<<srcCdfArr[i]<<std::endl;
}
// *** 求解目标图片的累积直方图(概率)分布 ***
memset(grayArr, 0, sizeof(grayArr));
for (size_t nrow = 0; nrow < dstRow; nrow++)
for (size_t ncol = 0; ncol < dstCol; ncol++)
{
int tag = dstImg.at<uchar>(nrow, ncol);
grayArr[tag]++;
}
tmp = 0;
for (int i = 0; i<graylevel; i++)
{
tmp += grayArr[i];
dstCdfArr[i] = tmp / (dstRow * dstCol);
}
// *** 直方图匹配算法 *** 这段代码看不懂的可以看帖子下面的代码说明
int histMap[256];
int minTag;
for (int i = 0; i<graylevel; i++)
{
float minMap = 10.f;
for (int j = 0; j<graylevel; j++)
{
if (minMap > abs(srcCdfArr[i] - dstCdfArr[j]))
{
minMap = abs(srcCdfArr[i] - dstCdfArr[j]);
minTag = j;
}
}
histMap[i] = minTag;
}
for (size_t nrow = 0; nrow < out.rows; nrow++)
for (size_t ncol = 0; ncol < out.cols; ncol++)
{
int tag = out.at<uchar>(nrow, ncol);
out.at<uchar>(nrow, ncol) = histMap[tag];
}
return out;
}
int main()
{
Mat srcImage_bgr = imread("E:/material/images/P0030-zhaolinger.jpg");
Mat grayImage_src;
cvtColor(srcImage_bgr, grayImage_src, CV_BGR2GRAY);
imshow("【待匹配的灰度图】", grayImage_src);
Mat dstImg_bgr = imread("E:/material/images/P0031-anhei.jpg");
Mat grayImage_dst;
cvtColor(dstImg_bgr, grayImage_dst, CV_BGR2GRAY);
imshow("【匹配目标的灰度图】", grayImage_dst);
Mat out;
out = ycMatchHist(grayImage_src, grayImage_dst);
imshow("【原图匹配之后】", out);
waitKey(0);
return 0;
}
这里解释下下面这段代码:
[C++] 纯文本查看 复制代码 int histMap[256];
int minTag;
for (int i = 0; i<graylevel; i++)
{
float minMap = 10.f;
for (int j = 0; j<graylevel; j++)
{
if (minMap > abs(srcCdfArr[i] - dstCdfArr[j]))
{
minMap = abs(srcCdfArr[i] - dstCdfArr[j]);
minTag = j;
}
}
histMap[i] = minTag;
}
for (size_t nrow = 0; nrow < out.rows; nrow++)
for (size_t ncol = 0; ncol < out.cols; ncol++)
{
int tag = out.at<uchar>(nrow, ncol);
out.at<uchar>(nrow, ncol) = histMap[tag];
}
问:上面这段代码做了什么?
答:这段代码实现了进行直方图匹配变换时所需映射表的建立,通过这个映射表,我们可以把源图中每个像素的灰度值映射到一个新的灰度值。
问:映射表建立的原则是什么?
答:两个图像的累积直方图概率分布的某两个灰度值之差最小,则建立一个映射。
问:思考这段代码的时候需要知道的一个关键点什么?
答:累积直方图概率分布是一个递增函数。
运行结果如下:
从运行结果可以看出,原图是“偏亮的”,而匹配目标图是“偏暗的”,经过直方图匹配后原图也变暗了。
原图的直方图如下:
匹配目标图的直方图如下:
原图经过直方图匹配后的直方图如下:
实现直方图匹配的Python代码如下:
注意:该Python代码将彩色图像转化为灰度图像,是在读取图像时操作的。
[Python] 纯文本查看 复制代码 # -*- coding: utf-8 -*-
# 出处:昊虹AI笔记网(hhai.cc)
# 用心记录计算机视觉和AI技术
# 博主微信/QQ 2487872782
# QQ群 271891601
# 欢迎技术交流与咨询
# OpenCV的版本为4.4.0
import cv2 as cv
import numpy as np
import sys
if __name__ == '__main__':
# 读取图像
image1 = cv.imread('E:/material/images/P0030-zhaolinger.jpg', 0)
image2 = cv.imread('E:/material/images/P0031-anhei.jpg', 0)
# 判断图片是否读取成功
if image1 is None or image2 is None:
print('Failed to read Hist_Match.png or equalLena.png.')
sys.exit()
# 计算两张图像的直方图
hist_image1 = cv.calcHist([image1], [0], None, [256], [0, 256])
hist_image2 = cv.calcHist([image2], [0], None, [256], [0, 256])
# 对直方图进行归一化
hist_image1 = cv.normalize(hist_image1, None, norm_type=cv.NORM_L1)
hist_image2 = cv.normalize(hist_image2, None, norm_type=cv.NORM_L1)
# 计算两张图像直方图的累计概率
hist1_cdf = np.zeros((256, ))
hist2_cdf = np.zeros((256, ))
hist1_cdf[0] = 0
hist2_cdf[0] = 0
for i in range(1, 256):
hist1_cdf[i] = hist1_cdf[i - 1] + hist_image1[i]
hist2_cdf[i] = hist2_cdf[i - 1] + hist_image2[i]
# 构建累计概率误差矩阵
diff_cdf = np.zeros((256, 256))
for k in range(256):
for j in range(256):
diff_cdf[k][j] = np.fabs((hist1_cdf[k] - hist2_cdf[j]))
# 生成LUT映射表
lut = np.zeros((256, ), dtype='uint8')
for m in range(256):
# 查找源灰度级为i的映射灰度和i的累计概率差值最小的规定化灰度
min_val = diff_cdf[m][0]
index = 0
for n in range(256):
if min_val > diff_cdf[m][n]:
min_val = diff_cdf[m][n]
index = n
lut[m] = index
result = cv.LUT(image1, lut)
# 展示结果
cv.imshow('Origin Image1', image1)
cv.imshow('Origin Image2', image2)
cv.imshow('Result', result)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果如下:
|