环境:vs2019+OpenCV4.5.1
实验内容:对图像进行对数变换和直方图均衡化处理,显示两种方法处理后的图像直方图
#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>
using namespace cv;
using namespace std;
Mat Histogram_Display(Mat& srcImage);
void logTransform1(cv::Mat srcImage, int c);
Mat equahist(const cv::Mat& image, cv::Mat& result);
int main()
{
//灰
Mat srcImage = imread("d:\\images\\Gray.jpg");
//亮
//Mat srcImage = imread("d:\\images\\bright1.png");
//彩色
//Mat srcImage = imread("d:\\images\\lena.jpg");
Mat result;
imshow("【原图】", srcImage);
Mat yuan = Histogram_Display(srcImage);
imshow("【原图直方图】", yuan);
//均衡化处理
Mat equahistResult = equahist(srcImage, result);
imshow("【均衡化】", equahistResult);
Mat equahistHistogram = Histogram_Display(equahistResult);
imshow("【均衡化直方图】", equahistHistogram);
//对数
//对数变换实现了扩展低灰度值而压缩高灰度值的效果 此时a大于1
float c = 2;
logTransform1(srcImage, c);
waitKey(0);
return 0;
}
//均衡化处理
Mat equahist(const cv::Mat& image, cv::Mat& result)
{
double count[256] = { 0 };
//这个意思是声明mymax并对i进行初始化为0
long mymax(0);
float map[256] = { 0 };
//创建一个指定大小(Size),指定类型 type(CV_8UC1, CV_16SC1, CV_32FC3)的图像矩阵的矩阵体
result.create(image.size(), image.type());
long x, y;
/*
行列与坐标系对应关系
行rows:Y (height)
列cols:X (width)
注意!注意!注意!
在Mat类型变量访问时下标是反着写的,即:按照(y, x)的关系形式访问
*/
//cols 列 x
int w = image.cols * image.channels();
//rows y
int h = image.rows;
for (y = 0; y < h; y++)
for (x = 0; x < w; x++) {
//当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data
count[*(image.data + y * w + x)]++;
}
//感觉这个循环的作用是把每个像素点变成浮点型,方便下个循环将累计直方图进行区间转换
for (int i = 0; i < 256; i++)
{
map[i] = 0;
float t = 0;
for (int j = 0; j <= i; j++)
{
t += 1.0 * count[j] / w / h;
}
map[i] = t;
}
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
{
/*
saturate_cast<uchar>此处为溢出保护,此处相当于 在剪切数值
>255 赋值为255;
<0 赋值为0;
*/
result.data[y * w + x] = cv::saturate_cast<uchar>(map[*(image.data + y * w+ x)] * 255);
}
return result;
}
//显示直方图
Mat Histogram_Display(Mat& srcImage)
{
//为计算直方图配置变量
//首先是需要计算的图像的通道,就是需要计算图像的哪个通道(bgr空间需要确定计算 b或g货r空间)
int channels = 0;
//然后是配置输出的结果存储的 空间 ,用MatND类型来存储结果
MatND dstHist;
//接下来是直方图的每一个维度的 柱条的数目(就是将数值分组,共有多少组)
int histSize[] = { 256 }; //如果这里写成int histSize = 256; 那么下面调用计算直方图的函数的时候,该变量需要写 &histSize
//最后是确定每个维度的取值范围,就是横坐标的总数
//首先得定义一个变量用来存储 单个维度的 数值的取值范围
float midRanges[] = { 0, 256 };
const float* ranges[] = { midRanges };
calcHist(&srcImage, 1, &channels, Mat(), dstHist, 1, histSize, ranges, true, false);
//calcHist 函数调用结束后,dstHist变量中将储存了 直方图的信息 用dstHist的模版函数 at<Type>(i)得到第i个柱条的值
//at<Type>(i, j)得到第i个并且第j个柱条的值
//开始直观的显示直方图——绘制直方图
//首先先创建一个黑底的图像,为了可以显示彩色,所以该绘制图像是一个8位的3通道图像
Mat drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
//因为任何一个图像的某个像素的总个数,都有可能会有很多,会超出所定义的图像的尺寸,针对这种情况,先对个数进行范围的限制
//先用 minMaxLoc函数来得到计算直方图后的像素的最大个数
double g_dHistMaxValue;
//minMaxLoc 函数的作用是在数组中找到全局最小和最大值
minMaxLoc(dstHist, 0, &g_dHistMaxValue, 0, 0);
//将像素的个数整合到 图像的最大范围内
//遍历直方图得到的数据
for (int i = 0; i < 256; i++)
{
//cvRound 返回和参数最接近的整数值
int value = cvRound(dstHist.at<float>(i) * 256 * 0.9 / g_dHistMaxValue);
/*
void cvLine( CvArr* img,
CvPoint pt1,
CvPoint pt2,
CvScalar color,
int thickness=1,
int line_type=8,
int shift=0 );
第一个参数img:要划的线所在的图像;
第二个参数pt1:直线起点
第二个参数pt2:直线终点
第三个参数color:直线的颜色 e.g:Scalor(0,0,255)
第四个参数thickness=1:线条粗细
第五个参数line_type=8,
*/
line(drawImage, Point(i, drawImage.rows - 1), Point(i, drawImage.rows - 1 - value), Scalar(255, 0, 0));
}
return drawImage;
}
// 对数变换方法1
void logTransform1(cv::Mat srcImage, int c)
{
// 输入图像判断
if (srcImage.empty())
std::cout << "No data!" << std::endl;
cv::Mat resultImage =
cv::Mat::zeros(srcImage.size(), srcImage.type());
// 计算 1 + r
cv::add(srcImage, cv::Scalar(1.0), srcImage);
// 转换为32位浮点数
srcImage.convertTo(srcImage, CV_32F);
// 计算 log(1 + r)
log(srcImage, resultImage);
resultImage = c * resultImage;
// 归一化处理
cv::normalize(resultImage, resultImage,
0, 255, cv::NORM_MINMAX);
cv::convertScaleAbs(resultImage, resultImage);
imshow("【log图】", resultImage);
Mat logImages = Histogram_Display(resultImage);
imshow("【log直方图】", logImages);
}