在图像预处理中,图像的灰度变换是图像增强的重要手段,灰度变换可以使图像对比度扩展,图像清晰,特征明显,灰度变换主要利用点运算来修正像素灰度,由输入像素点的灰度值确定相应输出点的灰度值,是一种基于图像变换的操作。
(1) 灰度化原理
灰度化处理就是将一幅色彩图像转化为灰度图像的过程。彩色图像分为R,G,B三个分量,分别显示出红绿蓝等各种颜色,灰度化就是使彩色的R,G,B分量相等的过程。灰度值大的像素点比较亮(像素值最大为255,为白色),反之比较暗(像素最下为0,为黑色)。
图像灰度化核心思想是 R = G = B ,这个值也叫灰度值。
图像灰度化的算法:
1)最大值法:使转化后的R,G,B得值等于转化前3个值中最大的一个,即:R=G=B=max(R,G,B)。这种方法转换的灰度图亮度很高。
2)平均值法:是转化后R,G,B的值为转化前R,G,B的平均值。即:R=G=B=(R+G+B)/3。这种方法产生的灰度图像比较柔和。
# Y = 0.299R + 0.587G + 0.114B
3)加权平均值法:按照一定权值,对R,G,B的值加权平均,即:Y = 0.299R + 0.587G + 0.114B,分别为R,G,B的权值,取不同的值形成不同的灰度图像。由于人眼对绿色最为敏感,红色次之,对蓝色的敏感性最低,因此使将得到较易识别的灰度图像。一般时,得到的灰度图像效果最好。
(2) 二值化原理
二值化核心思想,设阈值,大于阈值的为0(黑色)或 255(白色),使图像称为黑白图。
阈值可固定,也可以自适应阈值。
自适应阈值一般为一点像素与这点为中序的区域像素平均值或者高斯分布加权和的比较,其中可以设置一个差值也可以不设置。
void grayImageShow(Mat& input, Mat& output)
{
for (int i = 0; i < input.rows; ++i)
for (int j = 0; j < input.cols; ++j)
output.at(i, j) = saturate_cast(0.114 * input.at(i, j)[0] + 0.587 * input.at(i, j)[1] + 0.2989 * input.at(i, j)[2]);
imshow("由经验公式得到的灰度图像", output);
}
int main()
{
Mat src, gray, dst; //分别用来存储原图,灰度图
gray = imread("E:\\Lena.jpg", IMREAD_GRAYSCALE);//由imread()得到的灰度图像
src = imread("E:\\Lena.jpg");
dst.create(src.rows, src.cols, CV_8UC1); //创建原图尺寸大小的空白图
imshow("scr", src);
imshow("由imread得到的灰度图像", gray);
grayImageShow(src, dst);//由经验公式得到的灰度图像
waitKey(-1); //按键后再继续
return 0;
}
saturate_cast函数的作用即是:当运算完之后,结果为负,则转为0,结果超出255,则为255。(防止溢出)
访问(i,j)处像素
以8位(0~255)灰度图像和BGR彩色图像为例,用at可以访问图像像素:
//灰度图像:
image.at(i,j) //j为行数,i为列数
//BGR彩色图像
image.at(i,j)[0] //B分量
image.at(i,j)[1] //G分量
image.at(i,j)[2] //R分量
优化
Gray = (2989*R+5870*G+1140*B)/10000
=>> Gray = (4898*R+9618*G+1868*B)>>14
=>> Gray = (76*R+150*G+30*B)>>8
图像对比度指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,即指一幅图像灰度反差的大小。差异范围越大代表对比越大,图像越清晰;差异范围越小代表对比越小,图像越模糊。
y = k x + b y = kx+b y=kx+b
k > 1 时,输出图像的对比度增大;
k < 1 时,输出图像的对比度减小;
k = 1 且 b ≠ 0 时,所有图像的灰度值上移或者下移,其效果是是整个图像变亮或者变暗;
k = 1 且 b = 0 时,输入图像与输出图像相同;
k = -1 且 b = 255 时,输入图像的灰度正好反转;
k > 0 且 b > 0时,暗区域变量,亮区域变暗,点运算完成了图像求补运算。
#include
#include
using namespace cv;
using namespace std;
// 图像线性变换操作
Mat linearTransform(Mat srcImage, float k, int b)
{
if (srcImage.empty()) {
std::cout << "No data!" << std::endl;
}
const int nRows = srcImage.rows;
const int nCols = srcImage.cols;
Mat resultImage = Mat::zeros(srcImage.size(), srcImage.type());
// 图像元素遍历
for (int i = 0; i < nRows; i++)
{
for (int j = 0; j < nCols; j++)
{
for (int c = 0; c < 3; c++)//如果源图像是灰度图,那么把这里改为c<1即可
{
// 矩阵at操作,检查下标防止越界
resultImage.at(i, j)[c] = saturate_cast(k * (srcImage.at(i, j)[c]) + b);
}
}
}
return resultImage;
}
int main()
{
// 图像获取及验证
Mat src;
src = imread("E:\\Lena.jpg");
Size nSizeWindows = Size(2, 1);
// 大图像大小
Mat showWindowsImages(400, 820, CV_8UC3, Scalar(0, 0, 0));
//利用Rect区域将小图像置于大图像的相应区域
Mat tempImage = showWindowsImages(Rect(0, 0, 400, 400));
//利用resize函数实现图像缩放
resize(src, tempImage, Size(400, 400));
tempImage = showWindowsImages(Rect(420, 0, 400, 400));
Mat resImage(src.size(), src.type());
// 线性变换
float k = 1.5;
int b = 1;
Mat new_image = linearTransform(src, k, b);
resize(new_image, tempImage, Size(400, 400));
imshow("线性变换对比图", showWindowsImages);
waitKey(0);
return 0;
}
int main()
{
// 图像获取及验证
Mat src;
src = imread("E:\\Lena.jpg");
Size nSizeWindows = Size(2, 1);
// 大图像大小
Mat showWindowsImages(400, 820, CV_8UC3, Scalar(0, 0, 0));
//利用Rect区域将小图像置于大图像的相应区域
Mat tempImage = showWindowsImages(Rect(0, 0, 400, 400));
//利用resize函数实现图像缩放
resize(src, tempImage, Size(400, 400));
tempImage = showWindowsImages(Rect(420, 0, 400, 400));
Mat resImage(src.size(), src.type());
// 线性变换
float k = -1;
int b = 255;
int nRows = src.rows;
int nCols = src.cols;
Mat resultImage(src.size(), src.type());
for (int i = 0; i < nRows; i++)
{
for (int j = 0; j < nCols; j++)
{
for (int c = 0; c < 3; c++)//如果源图像是灰度图,那么把这里改为c<1即可
{
// 矩阵at操作,检查下标防止越界
resultImage.at(i, j)[c] = saturate_cast(k * (src.at(i, j)[c]) + b);
}
}
}
resize(resultImage, tempImage, Size(400, 400));
imshow("线性变换对比图", showWindowsImages);
waitKey(0);
return 0;
}
k > 1 时,输出图像的对比度增大
k < 1 时,输出图像的对比度减小
k = 1 且 b ≠ 0 时,所有图像的灰度值上移或者下移,其效果是是整个图像变亮或者变暗
k = 1 且 b = 0 时,输入图像与输出图像相同
k = -1 且 b = 255 时,输入图像的灰度正好反转
k > 0 且 b > 0时,暗区域变量,亮区域变暗,点运算完成了图像求补运算
1、bit_depth:比特数,有代表8bite\16bite\32bite\64bite
8表示你所创建的储存图片的Mat对象中,每个像素点在内存空间所占的空间大小8bite。
2、S|U|F
S: signed int,即有符号整型。
U: unsigned int,即无符号整型。
F: float,单精度浮点型。
3、:代表所存储的图片的通道数。
若为1:grayImg灰度图像,即单通道图像。
若为2:RGB彩色图像,即3通道图像。
若为3:带Alpha通道的RGB彩色图像,即4通道图像。
Scalar(a) 灰度值
1、Scalar(0) 黑色
2、Scalar(255) 白色
3、Scalar(100) 灰色
Scalar(B,G.R) BGR3通道颜色
Scalar(255,0,0) 蓝色
Scalar(0,255,0) 绿色
Scalar(B,G.R,C) Blue,Green,Red,Channels
Scalar(H,S.V)
hue色调,saturation饱和度,value亮度
其中x表示原图灰度,y表示变换后的图像灰度
// 分段线性拉伸
//fStart :分段区间起点
//fEnd :分段区间终点
//fSout :映射区间起点
//fEout :映射区间终点
void dividedLinearStrength(cv::Mat& matInput, cv::Mat& matOutput, float fStart, float fEnd,float fSout, float fEout)
{
float fK1 = fSout / fStart;
float fK2 = (fEout - fSout) / (fEnd - fStart);
float fC2 = fSout - fK2 * fStart;
float fK3 = (255.0f - fEout) / (255.0f - fEnd);
float fC3 = 255.0f - fK3 * 255.0f; //把点(255.255)和斜率fk3代入y=kx+b求截距
std::vector loolUpTable(256);
for (size_t m = 0; m < 256; m++)
{
if (m < fStart)
{
loolUpTable[m] = static_cast(m * fK1);
}
else if (m > fEnd)
{
loolUpTable[m] = static_cast(m * fK3 + fC3);
}
else
{
loolUpTable[m] = static_cast(m * fK2 + fC2);
}
}
matOutput = cv::Mat::zeros(matInput.rows, matInput.cols, matInput.type());
for (size_t r = 0; r < matInput.rows; r++)
{
unsigned char* pInput = matInput.data + r * matInput.step[0];
unsigned char* pOutput = matOutput.data + r * matOutput.step[0];
for (size_t c = 0; c < matInput.cols; c++)
{
pOutput[c] = loolUpTable[pInput[c]];
}
}
}
int main()
{
cv::Mat matSrc = cv::imread("E:\\Lena.jpg", 1);
cv::imshow("原始图", matSrc);
cv::Mat matDLS;
dividedLinearStrength(matSrc, matDLS, 72, 200, 5, 240);
cv::imshow("分段线性拉伸", matDLS);
cv::waitKey(0);
return 0;
}
int main()
{
Mat img1, img2;
img1 = imread("E:\\Lena.jpg", 1);
imshow("原图", img1);
img2 = Mat::zeros(img1.size(), 1);
for (int i = 0; i < img1.rows; i++)
{
for (int j = 0; j < img1.cols; j++)
{
uchar temp = img1.at(i, j);
if (temp <= 70)
{
img2.at(i, j) = saturate_cast(0.5 * temp + 20);
}
else if (temp > 70 && temp <= 150)
{
img2.at(i, j) = saturate_cast(1.2 * temp + 100);
}
else if (temp > 150 && temp <= 255)
{
img2.at(i, j) = saturate_cast(0.9 * temp + 55);
}
}
}
imshow("分段线性", img2);
waitKey(0);
}
对数变换原理:扩展图像中的暗像素值,压缩高灰度值。
// 对数变换方法1
Mat logTransform1( Mat srcImage, int c)
{
// 输入图像判断
if (srcImage.empty())
cout << "No data!" << endl;
Mat resultImage = Mat::zeros(srcImage.size(), srcImage.type());
// 计算 1 + r
add(srcImage, Scalar(1.0), srcImage);
// 转换为32位浮点数
srcImage.convertTo(srcImage, CV_32F);
// 计算 log(1 + r)
log(srcImage, resultImage);
resultImage = c * resultImage;
// 归一化处理
normalize(resultImage, resultImage,
0, 255, NORM_MINMAX);
convertScaleAbs(resultImage, resultImage);
return resultImage;
}
// 对数变换方法2
Mat logTransform2(Mat srcImage, float c)
{
// 输入图像判断
if (srcImage.empty())
cout << "No data!" << endl;
Mat resultImage =
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(i, j);
gray = c * log((double)(1 + gray));
resultImage.at(i, j) = saturate_cast(gray);
}
}
// 归一化处理
normalize(resultImage, resultImage,
0, 255, NORM_MINMAX);
convertScaleAbs(resultImage, resultImage);
return resultImage;
}
// 对数变换方法3
Mat logTransform3(Mat srcImage, float c)
{
// 输入图像判断
if (srcImage.empty())
cout << "No data!" << endl;
Mat resultImage =
Mat::zeros(srcImage.size(), srcImage.type());
srcImage.convertTo(resultImage, CV_32F);
resultImage = resultImage + 1;
log(resultImage, resultImage);
resultImage = c * resultImage;
normalize(resultImage, resultImage, 0, 255, NORM_MINMAX);
convertScaleAbs(resultImage, resultImage);
return resultImage;
}
int main()
{
// 读取灰度图像及验证
Mat srcImage = imread("E:\\Lena.jpg", 0);
if (!srcImage.data)
return -1;
// 验证三种不同方式的对数变换速度
imshow("原图", srcImage);
float c = 1.2;
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;
cout << "第一种方法耗时:" << tTime << endl;
imshow("效果图", resultImage);
waitKey(0);
return 0;
}
上面的实例给了三种方法,其中方法一和方法三是通过对矩阵整体操作来完成的,方法二是对图像中每个元素操作来完成的。方法一和方法三的区别是前者是对源图像进行对数操作,后者是对目标图像进行对数操作。
//归一化
//data 进行处理的像素集合
//grayscale 目标灰度级
//rows cols type 目标图像的行,列,以及类型
Mat Normalize(vector 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(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 value;
for (int r = 0; r < src.rows; r++)
{
uchar* srcRowData = src.ptr(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("E:\\Lena.jpg", 0);
if (srcImg.data == NULL)
{
cout << "图像打开失败" << endl;
return -1;
}
imshow("原图", srcImg);
//Mat dstImg = LogTransform(srcImg,0.2);
Mat dstImg;
dstImg = NegativeLogTransform(srcImg, 255);
imshow("变换后", dstImg);
waitKey(0);
return 0;
}
幂律变换也称伽马变换或指数变换,主要用于图像的校正,对漂白的图片或过黑的图片进行修正,增强对比度。
其中,c和γ为常数。伽马变换的效果与对数变换效果类似,当γ>1时,将较窄范围的低灰度值映射为较宽范围的灰度,同时将较宽范围的高灰度值映射为较窄范围的灰度值;当γ<1时,情况相反,与反对数变换类似。
γ>1时,低灰度区间压缩,高灰度区间拉伸;当γ<1时,低灰度区间拉伸,高灰度区间压缩;γ=1时,简化为恒等变换。
int main()
{
Mat src;
src = imread("E:\\Lena.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;
}