实验2 直方图均衡
实验要求
- 计算灰度图像的归一化直方图。
具体内容:利用OpenCV对图像像素进行操作,计算归一化直方图,并在 窗口中以图形的方式显示出来 - 灰度图像直方图均衡处理
具体内容:通过计算归一化直方图,设计算法实现直方图均衡化处理。 - 彩色图像直方图均衡处理
具体内容: 在灰度图像直方图均衡处理的基础上实现彩色直方图均衡处理。
实验完成情况
包括完成的实验内容及每个实验的完成程度
完成了4项内容:
- 显示归一化直方图
两个函数,一个CalcNormalizedHistogram()是本实验主要的一个函数,用于计算直方图数组以及直方图的图像。接口中,img是需要统计直方图的灰度图像,histImg是直方图图像,pmax指向直方图中最大值,hist是大小为256的直方图数组,用于统计每个灰度级像素点个数,color是直方图显示的颜色,主要针对BGR图像的直方图的显示。ShowNormalizedHistogram()就是简单显示一下直方图。int CalcNormalizedHistogram(Mat img, Mat histImg, int histHeight, int *pmax, int *hist, Scalar color) { //Mat lutTable = Mat::zeros(1, 256, CV_8U); //LUT(histImg, lutTable, histImg); memset(hist, 0, 256 * sizeof(int)); histImg = 0; int max = 0; for (int y = 0; y < img.rows; y++) { for (int x = 0; x < img.cols; x++) { hist[(int)img.at<uchar>(y, x)]++; max = hist[(int)img.at<uchar>(y, x)] > max ? hist[(int)img.at<uchar>(y, x)] : max; } } *pmax = max; //minMaxLoc(img, &min, &max, 0, 0); int binValue; for (int i=0; i < 256; ++i) { binValue = hist[i] * histHeight / max; rectangle(histImg, Point(i, histHeight), Point(i + 1, histHeight - binValue), color); } return *hist; } int ShowNormalizedHistogram() { int hist[256]; memset(hist, 0, 256 * sizeof(int)); int histHeight = 256; int max; int *pmax = &max; Mat histImg = Mat::zeros(histHeight, 256, CV_8U); //Mat histImg(histHeight, 256, CV_8U, Scalar(0)); CalcNormalizedHistogram(gray, histImg, histHeight, pmax, hist, Scalar(255)); namedWindow("Gray - 灰度图像", WINDOW_AUTOSIZE); imshow("Gray - 灰度图像", gray); namedWindow("Histogram - 直方图", WINDOW_AUTOSIZE); imshow("Histogram - 直方图", histImg); waitKey(0); destroyAllWindows(); return 0; } - 灰度图像直方图均衡处理
一开始按照课本上的算法进行直方图均衡,后来发现对于有较大黑暗区域的图像,比如实验用的moon.jpg,原始图像0灰度级经过均衡化计算后会映射到120左右,使得图片看起来偏灰,因此后来又对图片进行了一下标定,将均衡后的图像灰度级再拉伸到原始图像的灰度级范围内。一开始是在映射之后对图像进行的标定,与直接调用API的结果略有不同。后来发现应该在映射之前对灰度级进行标定,所得结果就跟调用官方API的一样了。int GrayHistogramEqualization() { int hist[256]; memset(hist, 0, 256 * sizeof(int)); int histHeight = 256; int max; int *pmax = &max; Mat histImg = Mat::zeros(histHeight, 256, CV_8U); CalcNormalizedHistogram(gray, histImg, histHeight, pmax, hist, Scalar(255)); Mat histEquImg = Mat::zeros(gray.size(), gray.type()); double table[256]; table[0] = 255. * double(hist[0]) / (double)(gray.rows*gray.cols); for (int i = 1; i < 256; i++) { table[i] = (255. * double(hist[i]) / double(gray.rows*gray.cols)) + table[i - 1]; } //normalize the table for (int i = 0; i < 256; i++) { table[i] = (255. * table[i] - table[0]) / (table[255] - table[0]); } for (int y = 0; y < gray.rows; y++) { for (int x = 0; x < gray.cols; x++) { histEquImg.at<uchar>(y, x) = saturate_cast<uchar>(int(table[gray.at<uchar>(y, x)] + 0.5));//int(double + 0.5)用来四舍五入 } } //normalize the histEquImg //double maxOrigin, minOrigin; //minMaxLoc(gray, &minOrigin, &maxOrigin, 0, 0); //LinearTransProcessing(histEquImg, histEquImg, (int)minOrigin, (int)maxOrigin); //minMaxLoc(histEquImg, &minOrigin, &maxOrigin, 0, 0); //histEquImg.convertTo(histEquImg, CV_8U, 255. / (maxOrigin - minOrigin), (-255.*minOrigin) / (maxOrigin - minOrigin)); namedWindow("Gray - 灰度图像", WINDOW_AUTOSIZE); imshow("Gray - 灰度图像", gray); namedWindow("Histogram - 直方图", WINDOW_AUTOSIZE); imshow("Histogram - 直方图", histImg); namedWindow("GrayHistogramEqualization - 直方图均衡后灰度图像", WINDOW_AUTOSIZE); imshow("GrayHistogramEqualization - 直方图均衡后灰度图像", histEquImg); CalcNormalizedHistogram(histEquImg, histImg, histHeight, pmax, hist, Scalar(255)); namedWindow("直方图均衡后灰度图像直方图", WINDOW_AUTOSIZE); imshow("直方图均衡后灰度图像直方图", histImg); Mat cv_HistEquImg = Mat::zeros(gray.size(), gray.type()); equalizeHist(gray, cv_HistEquImg); namedWindow("调用OpenCV直方图均衡化API得到的结果", WINDOW_AUTOSIZE); imshow("调用OpenCV直方图均衡化API得到的结果", cv_HistEquImg); Mat cv_HistImg = Mat::zeros(histHeight, 256, CV_8U); CalcNormalizedHistogram(cv_HistEquImg, cv_HistImg, histHeight, pmax, hist, Scalar(255)); namedWindow("调用OpenCV直方图均衡化API得到的结果的直方图", WINDOW_AUTOSIZE); imshow("调用OpenCV直方图均衡化API得到的结果的直方图", cv_HistImg); waitKey(0); destroyAllWindows(); return 0; } - BGR彩色图像三通道直方图均衡处理
三个通道BGR分别进行直方图均衡,最后合起来,但BGR的变换程度不一,会导致一个问题,就是原始图像的色度会发生改变。int BGRHistogramEqulization() { vector<Mat> BGRchannels; split(image, BGRchannels); Mat bChannel, gChannel, rChannel; bChannel = BGRchannels.at(0); gChannel = BGRchannels.at(1); rChannel = BGRchannels.at(2); int b_Hist[256], g_Hist[256], r_Hist[256]; memset(b_Hist, 0, 256 * sizeof(int)); memset(g_Hist, 0, 256 * sizeof(int)); memset(r_Hist, 0, 256 * sizeof(int)); int histHeight = 256; int bMax, gMax, rMax; int *b_pmax = &bMax, *g_pmax = &gMax, *r_pmax = &rMax; Mat b_HistImg = Mat::zeros(histHeight, 256, CV_8UC3); Mat g_HistImg = Mat::zeros(histHeight, 256, CV_8UC3); Mat r_HistImg = Mat::zeros(histHeight, 256, CV_8UC3); CalcNormalizedHistogram(bChannel, b_HistImg, histHeight, b_pmax, b_Hist, Scalar(255, 0, 0)); CalcNormalizedHistogram(gChannel, g_HistImg, histHeight, g_pmax, g_Hist, Scalar(0, 255, 0)); CalcNormalizedHistogram(rChannel, r_HistImg, histHeight, r_pmax, r_Hist, Scalar(0, 0, 255)); namedWindow("B channel", WINDOW_AUTOSIZE); namedWindow("G channel", WINDOW_AUTOSIZE); namedWindow("R channel", WINDOW_AUTOSIZE); imshow("G channel", gChannel); imshow("B channel", bChannel); imshow("R channel", rChannel); namedWindow("B channel histogram", WINDOW_AUTOSIZE); namedWindow("G channel histogram", WINDOW_AUTOSIZE); namedWindow("R channel histogram", WINDOW_AUTOSIZE); imshow("B channel histogram", b_HistImg); imshow("G channel histogram", g_HistImg); imshow("R channel histogram", r_HistImg); waitKey(0); Mat b_HistEquImg = Mat::zeros(gray.size(), gray.type()); Mat g_HistEquImg = Mat::zeros(gray.size(), gray.type()); Mat r_HistEquImg = Mat::zeros(gray.size(), gray.type()); double b_table[256], g_table[256], r_table[256]; b_table[0] = 255. * double(b_Hist[0]) / (double)(image.rows*image.cols); g_table[0] = 255. * double(g_Hist[0]) / (double)(image.rows*image.cols); r_table[0] = 255. * double(r_Hist[0]) / (double)(image.rows*image.cols); for (int i = 1; i < 256; i++) { b_table[i] = (255. * double(b_Hist[i]) / (double)(image.rows*image.cols)) + b_table[i - 1]; g_table[i] = (255. * double(g_Hist[i]) / (double)(image.rows*image.cols)) + g_table[i - 1]; r_table[i] = (255. * double(r_Hist[i]) / (double)(image.rows*image.cols)) + r_table[i - 1]; } for (int y = 0; y < image.rows; y++) { for (int x = 0; x < image.cols; x++) { b_HistEquImg.at<uchar>(y, x) = saturate_cast<uchar>(int(b_table[bChannel.at<uchar>(y, x)] + 0.5)); g_HistEquImg.at<uchar>(y, x) = saturate_cast<uchar>(int(g_table[gChannel.at<uchar>(y, x)] + 0.5)); r_HistEquImg.at<uchar>(y, x) = saturate_cast<uchar>(int(r_table[rChannel.at<uchar>(y, x)] + 0.5)); } } //normalize the histEquImg double maxOrigin, minOrigin; minMaxLoc(bChannel, &minOrigin, &maxOrigin, 0, 0); LinearTransProcessing(b_HistEquImg, b_HistEquImg, (int)minOrigin, (int)maxOrigin); minMaxLoc(gChannel, &minOrigin, &maxOrigin, 0, 0); LinearTransProcessing(g_HistEquImg, g_HistEquImg, (int)minOrigin, (int)maxOrigin); minMaxLoc(rChannel, &minOrigin, &maxOrigin, 0, 0); LinearTransProcessing(r_HistEquImg, r_HistEquImg, (int)minOrigin, (int)maxOrigin); BGRchannels.at(0) = b_HistEquImg; BGRchannels.at(1) = g_HistEquImg; BGRchannels.at(2) = r_HistEquImg; Mat BGRHistEquImg; merge(BGRchannels, BGRHistEquImg); CalcNormalizedHistogram(b_HistEquImg, b_HistImg, histHeight, b_pmax, b_Hist, Scalar(255, 0, 0)); CalcNormalizedHistogram(g_HistEquImg, g_HistImg, histHeight, g_pmax, g_Hist, Scalar(0, 255, 0)); CalcNormalizedHistogram(r_HistEquImg, r_HistImg, histHeight, r_pmax, r_Hist, Scalar(0, 0, 255)); imshow("B channel", b_HistEquImg); imshow("G channel", g_HistEquImg); imshow("R channel", r_HistEquImg); imshow("B channel histogram", b_HistImg); imshow("G channel histogram", g_HistImg); imshow("R channel histogram", r_HistImg); waitKey(0); destroyAllWindows(); namedWindow("Original - 原始图像", WINDOW_AUTOSIZE); imshow("Original - 原始图像", image); namedWindow("BGRHistogramEqulization", WINDOW_AUTOSIZE); imshow("BGRHistogramEqulization", BGRHistEquImg); waitKey(0); destroyAllWindows(); return 0; } - HSV彩色图像V通道直方图均衡处理
为了不改变图像色度,将BGR图像转换为HSV图像,只对亮度进行直方图均衡,在不改变图像色度的情况下实现对图像的增强。int HSVHistogramEqulization() { vector<Mat> HSVchannels; split(hsv, HSVchannels); Mat hChannel, sChannel, vChannel; hChannel = HSVchannels.at(0); sChannel = HSVchannels.at(1); vChannel = HSVchannels.at(2); int v_Hist[256]; memset(v_Hist, 0, 256 * sizeof(int)); int histHeight = 256; int hMax, sMax, vMax; int *h_pmax = &hMax, *s_pmax = &sMax, *v_pmax = &vMax; Mat v_HistImg = Mat::zeros(histHeight, 256, CV_8UC3); CalcNormalizedHistogram(vChannel, v_HistImg, histHeight, v_pmax, v_Hist, Scalar(255, 255, 255)); namedWindow("V channel", WINDOW_AUTOSIZE); imshow("V channel", vChannel); namedWindow("V channel Histogram", WINDOW_AUTOSIZE); imshow("V channel Histogram", v_HistImg); waitKey(0); Mat v_HistEquImg = Mat::zeros(gray.size(), gray.type()); double v_table[256]; v_table[0] = 255. * double(v_Hist[0]) / (double)(image.rows*image.cols); for (int i = 1; i < 256; i++) { v_table[i] = (255. * double(v_Hist[i]) / (double)(image.rows*image.cols)) + v_table[i - 1]; } for (int y = 0; y < image.rows; y++) { for (int x = 0; x < image.cols; x++) { v_HistEquImg.at<uchar>(y, x) = saturate_cast<uchar>(int(v_table[vChannel.at<uchar>(y, x)] + 0.5)); } } //normalize the histEquImg double maxOrigin, minOrigin; minMaxLoc(vChannel, &minOrigin, &maxOrigin, 0, 0); LinearTransProcessing(v_HistEquImg, v_HistEquImg, (int)minOrigin, (int)maxOrigin); HSVchannels.at(2) = v_HistEquImg; Mat HSVHistEquImg; merge(HSVchannels, HSVHistEquImg); cvtColor(HSVHistEquImg, HSVHistEquImg, CV_HSV2BGR); CalcNormalizedHistogram(v_HistEquImg, v_HistImg, histHeight, v_pmax, v_Hist, Scalar(255, 255, 255)); imshow("V channel", v_HistEquImg); imshow("V channel Histogram", v_HistImg); waitKey(0); destroyAllWindows(); namedWindow("Original - 原始图像", WINDOW_AUTOSIZE); imshow("Original - 原始图像", image); namedWindow("HSVHistogramEqulization", WINDOW_AUTOSIZE); imshow("HSVHistogramEqulization", HSVHistEquImg); waitKey(0); destroyAllWindows(); return 0; }
实验中的问题
包括在实验中遇到的问题,以及解决问题的方法
-
主要的问题就是直方图均衡的算法,要在课本上的算法之后再对图像进行一下拉伸。我是按照原始图像的灰度范围进行的拉伸,调用了OpenCV的资方图均衡API发现OpenCV中的equalizeHist是将图像灰度级拉伸到[0,255]区间内。
-
算法当中一个小问题,包括所有double型计算中,整型(包括int,uchar等)与double掺杂运算需要对整型数据进行类型转换,并且double转到整型时需要进行四舍五入,不然只会取整数位在很多情况下会造成1灰度级的误差。我的代码中直接用int(double + 0.5)进行的四舍五入。
-
程序=算法+数据结构,这几次实验的算法都不算复杂,只需要注意几个小坑,不然算法出了类似四舍五入一类的小问题很不容易发现,我现在的直方图均衡代码,虽然大的方面从直方图图像以及增强效果来看已经没问题了,也依旧不能完全肯定里面是否仍有些小问题。
-
另外会比较容易出错以及浪费时间的是数据结构,包括数据类型的用法,函数接口的使用,个别时候还可能因为C/C++的语法不熟出问题。这都需要高效的信息检索能力,需要找到相关资料确定用法以及可能的错误。不过这都是开始时的问题,慢慢写的多了熟了这类基础问题不会再发生。
-
这次实验第2部分没有写一个接口,导致后面BGR和HSV都有大量重复代码,复制粘贴然后机械重复修改耗时耗力而且没什么意义,是需要在今后实验中改进的地方。