OpenCV数字图像处理基于C++:灰度变换

OpenCV数字图像处理基于C++:灰度变换

1.1 灰度变换概念

图像预处理中,图像的灰度变换是图像增强的重要手段,灰度变换可以使图像对比度扩展,图像清晰,特征明显,灰度变换主要利用点运算来修正像素灰度,由输入像素点的灰度值确定相应输出点的灰度值,是一种基于图像变换的操作。

1.2 灰度变换的作用

  1. 改善图像是质量,显示更多的细节,提高图像的对比度;
  2. 有选择的突出图像感兴趣的特征或者抑制图像中不需要的特征;
  3. 可以有效的改变图像的直方图的分布,使像素的分布更加均匀。

1.3 灰度变换的方法

  1. 线性灰度变换
  2. 分段线性灰度变换
  3. 非线性灰度变换(对数变换,幂律变换(伽马变换))

1.4 灰度化

(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(白色),使图像称为黑白图。
  阈值可固定,也可以自适应阈值。
  自适应阈值一般为一点像素与这点为中序的区域像素平均值或者高斯分布加权和的比较,其中可以设置一个差值也可以不设置。

1.5 实现 RGB 图像转灰度图像

void grayImageShow(Mat& input, Mat& output)
{
	for (int i = 0; i < input.rows; ++i)
		for (int j = 0; j < input.cols; ++j)
			output.at<uchar>(i, j) = saturate_cast<uchar>(0.114 * input.at<Vec3b>(i, j)[0] + 0.587 * input.at<Vec3b>(i, j)[1] + 0.2989 * input.at<Vec3b>(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;
}

image-20221006090429126

saturate_cast函数的作用即是:当运算完之后,结果为负,则转为0,结果超出255,则为255。(防止溢出)

访问(i,j)处像素

以8位(0~255)灰度图像和BGR彩色图像为例,用at可以访问图像像素:

//灰度图像:
image.at<uchar>(i,j) //j为行数,i为列数
//BGR彩色图像
image.at<Vec3b>(i,j)[0] //B分量
image.at<Vec3b>(i,j)[1] //G分量
image.at<Vec3b>(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

1.6 对比度

图像对比度指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,即指一幅图像灰度反差的大小。差异范围越大代表对比越大,图像越清晰;差异范围越小代表对比越小,图像越模糊。

1.7 线性变换

OpenCV数字图像处理基于C++:灰度变换

k > 1 时,输出图像的对比度增大;
k < 1 时,输出图像的对比度减小;
k = 1 且 b ≠ 0 时,所有图像的灰度值上移或者下移,其效果是是整个图像变亮或者变暗;
k = 1 且 b = 0 时,输入图像与输出图像相同;
k = -1 且 b = 255 时,输入图像的灰度正好反转;
k > 0 且 b > 0时,暗区域变量,亮区域变暗,点运算完成了图像求补运算。

1.8 实现图像线性变换

#include<opencv2/opencv.hpp>
#include<iostream>

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<Vec3b>(i, j)[c] = saturate_cast<uchar>(k * (srcImage.at<Vec3b>(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<Vec3b>(i, j)[c] = saturate_cast<uchar>(k * (src.at<Vec3b>(i, j)[c]) + b);
            }
        }
    }
  
    resize(resultImage, tempImage, Size(400, 400));
    imshow("线性变换对比图", showWindowsImages);
    waitKey(0);
    return 0;
}

k > 1 时,输出图像的对比度增大

image-20221006100818601

k < 1 时,输出图像的对比度减小

image-20221006101048982

k = 1 且 b ≠ 0 时,所有图像的灰度值上移或者下移,其效果是是整个图像变亮或者变暗

image-20221006101213104

image-20221006101236053

k = 1 且 b = 0 时,输入图像与输出图像相同

image-20221006101259576

k = -1 且 b = 255 时,输入图像的灰度正好反转

image-20221006101324370k > 0 且 b > 0时,暗区域变量,亮区域变暗,点运算完成了图像求补运算

image-20221006101425073

img

1、bit_depth:比特数,有代表8bite\16bite\32bite\64bite
    8表示你所创建的储存图片的Mat对象中,每个像素点在内存空间所占的空间大小8bite。
    
2、S|U|F
    S: signed int,即有符号整型。
    U: unsigned int,即无符号整型。
    F: float,单精度浮点型。
    
3、<number_of_channels>:代表所存储的图片的通道数。
    若为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亮度

1.9 分段线性变换

img

其中x表示原图灰度,y表示变换后的图像灰度

1.10 实现分段变换核心算法

// 分段线性拉伸
//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<unsigned char> loolUpTable(256);
	for (size_t m = 0; m < 256; m++)
	{
		if (m < fStart)
		{
			loolUpTable[m] = static_cast<unsigned char>(m * fK1);
		}
		else if (m > fEnd)
		{
			loolUpTable[m] = static_cast<unsigned char>(m * fK3 + fC3);
		}
		else
		{
			loolUpTable[m] = static_cast<unsigned char>(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;
}

image-20221006103628076

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<uchar>(i, j);
			if (temp <= 70)
			{
				img2.at<uchar>(i, j) = saturate_cast<uchar>(0.5 * temp + 20);
			}
			else if (temp > 70 && temp <= 150)
			{
				img2.at<uchar>(i, j) = saturate_cast<uchar>(1.2 * temp + 100);
			}
			else if (temp > 150 && temp <= 255)
			{
				img2.at<uchar>(i, j) = saturate_cast<uchar>(0.9 * temp + 55);
			}
		}
	}
	imshow("分段线性", img2);
	waitKey(0);
}

image-20221006103908297

image-20221006103815009

1.11 对数变换

对数变换原理:扩展图像中的暗像素值,压缩高灰度值。

img

1.12 实现灰度对数变换

// 对数变换方法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<uchar>(i, j);
			gray = c * log((double)(1 + gray));
			resultImage.at<uchar>(i, j) = saturate_cast<uchar>(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;
}

image-20221006105619273

上面的实例给了三种方法,其中方法一和方法三是通过对矩阵整体操作来完成的,方法二是对图像中每个元素操作来完成的。方法一和方法三的区别是前者是对源图像进行对数操作,后者是对目标图像进行对数操作。

1.13 反对数变换

//归一化
//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("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;

}

image-20221006111422201

1.14 幂律变换

幂律变换也称伽马变换或指数变换,主要用于图像的校正,对漂白的图片或过黑的图片进行修正,增强对比度。

img

其中,c和γ为常数。伽马变换的效果与对数变换效果类似,当γ>1时,将较窄范围的低灰度值映射为较宽范围的灰度,同时将较宽范围的高灰度值映射为较窄范围的灰度值;当γ<1时,情况相反,与反对数变换类似。

γ>1时,低灰度区间压缩,高灰度区间拉伸;当γ<1时,低灰度区间拉伸,高灰度区间压缩;γ=1时,简化为恒等变换。

1.15 实现幂律变换

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

image-20221006112352521

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2022年10月8日 下午9:18
下一篇 2023年2月5日 下午1:40

相关推荐