OpenCV计算机视觉开发入门与实践<二十>:非线性变换灰度变换

1. 对数变换

  对数变换和反对数变换都属于非线性变换。对数变换的公式为:s=c×log(1+r),其中c为常数,r≥0。对数变换有两个作用:

  • 因为对数曲线在像素值较低的区域斜率较大,在像素值较高的区域斜率较小,所以图像经过对数变换之后,较暗区域的对比度将得到提升,因而能增强图像暗部的细节。
  • 图像的傅里叶频谱的动态范围可能宽达0~106。如果直接显示频谱,显示设备的动态范围往往不能满足要求,这个时候就需要使用对数变换,使得傅里叶频谱的动态范围被合 理地非线性压缩。

  在OpenCV中,图像对数变换的实现可以直接通过对图像中的每个元素运用上述公式来完成,也可以通过矩阵整体操作来完成。下面的实例中给出了3种方法(请看代码注释),其中方法一和方法三都是通过矩阵整体操作来完成的,方法二是通过对图像中每个元素进行操作来完成的。方法一和方法三的区别是前者是对原图像进行对数运算,后者是对目标图像进行对数运算。

【例5.6】实现灰度对数变换

方法一:

bash 复制代码
The time consumed by the first method:12.7645

方法二:

bash 复制代码
The time consumed by the first method:15.2897

方法三:

bash 复制代码
The time consumed by the method:2.52928
cpp 复制代码
#include <iostream>
#include<opencv2/opencv.hpp>
using namespace cv;  //所有opencv类都在命名空间cv下
using namespace std;
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库

// 对数变换方法一
cv::Mat 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);
	return resultImage;
}

// 对数变换方法二
cv::Mat logTransform2(Mat srcImage, float c)
{
	// 输入图像判断
	if (srcImage.empty())
		std::cout << "No data!" << std::endl;
	cv::Mat resultImage =
		cv::Mat::zeros(srcImage.size(), srcImage.type());
	double gray = 0;
	// 图像遍历分别计算每个像素点的对数变换
	for (int i = 0; i < srcImage.rows; i++) {
		for (int j = 0; j < srcImage.cols; j++) {
			gray = (double)srcImage.at<uchar>(i, j);
			gray = c * log((double)(1 + gray));
			resultImage.at<uchar>(i, j) = saturate_cast<uchar>(gray);
		}
	}
	// 归一化处理
	cv::normalize(resultImage, resultImage,
		0, 255, cv::NORM_MINMAX);
	cv::convertScaleAbs(resultImage, resultImage);
	return resultImage;
}

// 对数变换方法三
cv::Mat logTransform3(Mat srcImage, float c)
{
	// 输入图像判断
	if (srcImage.empty())
		std::cout << "No data!" << std::endl;
	cv::Mat resultImage =
		cv::Mat::zeros(srcImage.size(), srcImage.type());
	srcImage.convertTo(resultImage, CV_32F);
	resultImage = resultImage + 1;
	cv::log(resultImage, resultImage);
	resultImage = c * resultImage;
	cv::normalize(resultImage, resultImage, 0, 255, cv::NORM_MINMAX);
	cv::convertScaleAbs(resultImage, resultImage);
	return resultImage;
}

int main()
{
	// 读取灰度图像及验证
	cv::Mat srcImage = cv::imread("lakeWater.jpg", 0);
	if (!srcImage.data)
		return -1;

	// 验证三种不同方式的对数变换速度
	cv::imshow("src", srcImage);
	float c = 1.2;
	cv::Mat resultImage;
	double tTime;
	tTime = (double)getTickCount();
	const int nTimes = 10;
	for (int i = 0; i < nTimes; i++)
	{
		resultImage = logTransform1(srcImage, c);
	}

	tTime = 1000 * ((double)getTickCount() - tTime) /
		getTickFrequency();
	tTime /= nTimes;
	//输出第一种方法的耗时
	std::cout << " The time consumed by the first method:" << tTime << endl;	cv::imshow("result", resultImage);
	cv::waitKey(0);
	return 0;
}

2. 反对数变换

  反对数变换的公式是s = ((v + 1)r - 1) / v,我们直接以实例来说明。

【例5.7】实现灰度反对数变换

cpp 复制代码
//归一化
//data				进行处理的像素集合
//grayscale			目标灰度级
//rows cols type	目标图像的行、列以及类型
Mat Normalize(vector<double> data, int grayscale, int rows, int cols, int type)
{
	double max = 0.0;
	double min = 0.0;
	for (int i = 0; i < data.size(); i++)
	{
		if (data[i] > max)
			max = data[i];
		if (data[i] < min)
			min = data[i];
	}
	Mat dst;
	dst.create(rows, cols, type);
	int index = 0;
	for (int r = 0; r < dst.rows; r++)
	{
		uchar* dstRowData = dst.ptr<uchar>(r);
		for (int c = 0; c < dst.cols; c++)
		{
			dstRowData[c] = (uchar)(grayscale * ((data[index++] - min) * 1.0 / (max - min)));
		}
	}
	return dst;
}

//反对数变换
Mat NegativeLogTransform(Mat src, double parameter)
{
	vector<double> value;
	for (int r = 0; r < src.rows; r++)
	{
		uchar* srcRowData = src.ptr<uchar>(r);
		for (int c = 0; c < src.cols; c++)
		{
			//反对数变换公式为s = ((v + 1) ^ r - 1) / v
			value.push_back((pow(parameter + 1, srcRowData[c]) - 1) / parameter);
		}
	}
	//计算得出的s经过对比拉升(将像素值归一化到0~255)得到最终的图像
	return Normalize(value, 255, src.rows, src.cols, src.type());
}

int main()
{
	Mat srcImg = imread("gza.jpg", 0);  //读取源图片转为灰度图
	if (srcImg.data == NULL)
	{
		cout << "Fail to open file." << endl;
		return -1;
	}
	imshow("src", srcImg);
	//Mat dstImg = LogTransform(srcImg,0.2);
	Mat dstImg;
	dstImg = NegativeLogTransform(srcImg, 3);
	imshow("result", dstImg);
	waitKey(0);
	return 0;
}

3. 幂律变换

  幂律变换也称伽马变换或指数变换,是另一种常用的灰度非线性变换,主要用于图像的校正,对漂白的图片或者过黑的图片进行修正,也就是对灰度过高或者灰度过低的图片进行修正,增强对比度。幂律变换的公式如下:

  其中系数c和指数γ为正常数。伽马变换的效果与对数变换有点类似,当γ>1时,将较窄范围的低灰度值映射为较宽范围的灰度值,同时将较宽范围的高灰度值映射为较窄范围的灰度值,即低灰度区间压缩,高灰度区间拉伸;当γ小于1时,低灰度区间拉伸,高灰度区间压缩;当γ等于1时,简化成恒等变换。不同γ值对灰度区间进行拉伸或者压缩的效果如图所示。

【例5.8】实现幂律变换

cpp 复制代码
#include <iostream>
#include<opencv2/opencv.hpp>
using namespace cv;  //所有opencv类都在命名空间cv下
using namespace std;

int main()
{
	Mat src;
	src = imread("ball.jpg");
	if (src.empty()) //检验是否成功导入数据
	{
		cout << "not open successed!" << endl;
		return -1;
	}
	namedWindow("input", 0);
	imshow("input", src); // 显示输入的图像src
	cvtColor(src, src, COLOR_RGB2GRAY);
	Mat grayimg;
	grayimg.create(src.size(), src.type()); //创建一个大小类型相同的图像矩阵序列,也可以使用clone()函数
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < height; i++)
		for (int j = 0; j < width; j++)
		{
			int gray = src.at< uchar>(i, j);
			grayimg.at< uchar>(i, j) = pow(gray, 0.5);//将灰度值开方
		}
	normalize(grayimg, grayimg, 0, 255, NORM_MINMAX);//归一化,将数据归一到0~255
	imshow("output", grayimg);//显示图像grayimg
	waitKey(0);
	return 0;
}
相关推荐
dehuisun3 分钟前
第07篇:RAG+Agent 部署架构、资源评估与私有化方案
人工智能
米小虾7 分钟前
拆给 8 个子智能体,只拿回 2.3 倍信息:多智能体分解的产出守恒律
人工智能·agent
虹科网络安全9 分钟前
Redis 安全公告:CVE-2026-81934 TLS 处理漏洞及修复建议
网络·人工智能·网络安全
IT·陈寒10 分钟前
Redis 连接池泄漏害我加班到凌晨三点
人工智能·大模型·api·创业·变现·简历优化
西安栈上月明软件科技11 分钟前
从业务黑话到本体图谱:OAG本体建模五步法(西安老系统AI化改造实战)
数据库·人工智能·架构
带鱼吃猫21 分钟前
LangGraph入门:搭建智能快递配送系统AI工作流
人工智能·langchain
sjh75242296923 分钟前
RNN 是个啥?一个“边读边记小本本”的神经网络
人工智能
GG-_-Bond24 分钟前
9.1 kv存储resp协议模拟面试
c++
SEO_juper36 分钟前
Java 并发编程实战:从线程基础到高并发架构
运维·人工智能·爬虫·chatgpt·seo
dehuisun38 分钟前
第 04 篇:主流向量数据库选型决策(Milvus/Qdrant/pgvector/ 金仓 /openGauss)
人工智能