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;
}
