十一、OpenCV实现图像的灰度变换 图像处理基础(7):图像的灰度变换 数字图像处理-空间域处理-灰度变换-基本灰度变换函数(反转变换、对数变换、伽马变换和分段线性变换)

一、灰度变换的基本概念

  灰度变换指对图像的单个像素进行操做,主要以对比度和阈值处理为目的。其变换形式以下:html

  s=T(r)ios

其中,T是灰度变换函数;r是变换前的灰度;s是变换后的像素。
图像灰度变换的有如下做用:c++

    • 改善图像的质量,使图像可以显示更多的细节,提升图像的对比度(对比度拉伸)
    • 有选择的突出图像感兴趣的特征或者抑制图像中不须要的特征
    • 能够有效的改变图像的直方图分布,使像素的分布更为均匀

2经常使用的灰度变换说明

  灰度变换函数描述了输入灰度值和输出灰度值之间变换关系,一旦灰度变换函数肯定下来了,那么其输出的灰度值也就肯定了。可见灰度变换函数的性质就决定了灰度变换所能达到的效果。用于图像灰度变换的函数主要有如下几种:
  • 线性函数 (图像反转)
  • 对数函数:对数和反对数变换
  • Gamma变换:n次幂和n次开方变换
  • 分段线性变换

  上图给出了几种常见灰度变换函数的曲线图,根据这几种常见函数的曲线形状,能够知道这几种变换的所能达到的效果。例如,对数变换和幂律变换都能实现图像灰度级的扩展/压缩,另外对数变换还有一个重要的性质,它能压缩图像灰度值变换较大的图像的动态范围(例如,傅立叶变换的频谱显示)。

三、线性变换

   在图像处理基础运算曾经说过 ,经过作线性变换改变单个像素点的值能够调节整幅图像的亮度和对比度,即对像素进行线性变换,令r为变换前的灰度,s为变换后的灰度,则线性变换的函数:
S=ar+b

其中,a为直线的斜率,b为在y轴的截距。选择不一样的a,b值会有不一样的效果:ide

  • a>1,增长图像的对比度
  • a<1,减少图像的对比度
  • a=0,b!=0,图像变亮或变暗
  • a<0且b=0,图像的亮区域变暗,暗区域变亮
  • a=-1,b=255,图像亮度反转

OpenCV的实现以下:函数

灰度图实现:post

for (int i = 0; i < srcImg.rows; i++)
	{
		uchar *srcData = srcImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; j++)
		{
			dstImg.at<uchar>(i, j) = srcData[j] * k + b;
		}
	}

彩色图的实现只需拓展到三通道便可:ui

for (int i = 0; i < RowsNum; i++)
	{
		for (int j = 0; j < ColsNum; j++)
		{
			//c为遍历图像的三个通道
			for (int c = 0; c < 3; c++)
			{
				//使用at操做符,防止越界
				dstImg.at<Vec3b>(i, j)[c] = saturate_cast<uchar>
					(k* (srcImg.at<Vec3b>(i, j)[c]) + b);
 
			}
		}
	}

示例:

#include "stdafx.h"

#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat srcImg = imread("rice.png",0);
	if (!srcImg.data)
	{
		cout << "读入图片失败" << endl;
		return -1;
	}
	double k, b;
	cout << "请输入k和b值:";
	cin >> k >> b;
	int RowsNum = srcImg.rows;
	int ColsNum = srcImg.cols;
	Mat dstImg(srcImg.size(), srcImg.type());
	//进行遍历图像像素,对每一个像素进行相应的线性变换
	for (int i = 0; i < srcImg.rows; i++)
	{
		uchar *srcData = srcImg.ptr<uchar>(i);
		for (int j = 0; j < srcImg.cols; j++)
		{
			dstImg.at<uchar>(i, j) = srcData[j] * k + b;
		}
	}
	imshow("原图", srcImg);
	imshow("线性变换后的图像", dstImg);
	waitKey();
	return 0;
}

 程序运行结果以下:url

 

 

四、对数变换

  对数变换的通用公式是:spa

s=log(1+r)/b
  其中,b是一个常数,用来控制曲线的弯曲程度,其中,b越小越靠近y轴,b越大越靠近x轴。表达式中的r是原始图像中的像素值,s是变换后的像素值,能够分析出,当函数自变量较低时,曲线的斜率很大,而自变量较高时,曲线的斜率变得很小。 正是由于对数变.net

换具备这种压缩数据的性质,使得它可以实现图像灰度拓展和压缩的功能。即对数变换能够拓展低灰度值而压缩高灰度级值,让图像的灰度分布更加符合人眼的视觉特征。

  假设r≥0,根据上图中的对数函数的曲线能够看出:对数变换,将源图像中范围较窄的低灰度值映射到范围较宽的灰度区间,同时将范围较宽的高灰度值区间映射为较窄的灰度区间,从而扩展了暗像的值,压缩了高灰度的值,可以对图像中低灰度细节进行增

强。;从函数曲线也能够看出,反对数函数的曲线和对数的曲线是对称的,在应用到图像变换其结果是相反的,反对数变换的做用是压缩灰度值较低的区间,扩展高灰度值的区间。

经过OpenCV实现程序有三种,这里就不一一列举了,在示例中给出:

示例:

#include "stdafx.h"
#include <opencv2/core/core.hpp>        
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp>  
#include <iostream> 
using namespace cv;
// 对数变换方法1
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;
}
// 对数变换方法2
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;
}
// 对数变换方法3
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("111.jpg", 0);
	if (!srcImage.data)
		return -1;
	// 验证三种不一样方式的对数变换速度
	cv::imshow("srcImage", srcImage);
	float c = 2;
	cv::Mat resultImage;
	double tTime;
	tTime = (double)getTickCount();
	const int nTimes = 10;
	for (int i = 0; i < nTimes; i++)
	{
		resultImage = logTransform3(srcImage, c);
	}
	tTime = 1000 * ((double)getTickCount() - tTime) /
		getTickFrequency();
	tTime /= nTimes;
	std::cout << "第三种方法耗时:" << tTime << std::endl;
	cv::imshow("resultImage", resultImage);
	cv::waitKey(0);
	return 0;
}

 三种方法运行效果分别以下:

 

 

 

五、伽马变换

  基于幂次变换的Gamma校订是图像处理中一种很是重要的非线性变换,它与对数变换相反,它是对输入图像的灰度值进行指数变换,进而校订亮度上的误差。一般Gamma校订长应用于拓展暗调的细节。伽马变换的公式为:

  s=crγ

其中c和 γ为正常数.,伽马变换的效果与对数变换有点相似,当 γ >1时将较窄范围的低灰度值映射为较宽范围的灰度值,同时将较宽范围的高灰度值映射为较窄范围的灰度值;当 γ <1时,状况相反,与反对数变换相似。其函数曲线以下:

 

 

 

当γ<1时,图像的高光部分被扩展而暗调备份被压缩,γ的值越小,对图像低灰度值的扩展越明显;当γ>1时,图像的高光部分被压缩而暗调部分被扩展,γ的值越大,对图像高灰度值部分的扩展越明显。这样就可以显示更多的图像的低灰度或者高灰度细节。

  • 当γ<1时,低灰度区域动态范围扩大,进而图像对比度加强,高灰度值区域动态范围减少,图像对比度下降,图像总体灰度值增大,此时与图像的对数变换相似。
  • γ>1时,低灰度区域的动态范围减少进而对比度下降,高灰度区域动态范围扩大,图像的对比度提高,图像的总体灰度值变小,Gamma校订主要应用在图像加强。

总之,r<1的幂函数的做用是提升图像暗区域中的对比度,而下降亮区域的对比度;r>1的幂函数的做用是提升图像中亮区域的对比度,下降图像中按区域的对比度。因此Gamma变换主要用于图像的校订,对于灰度级总体偏暗的图像,可使用r<1的幂函数增大动态范围。对于灰度级总体偏亮的图像,可使用r>1的幂函数增大灰度动态范围。

基于OpenCV的实现:

Mat GammaTrans(Mat &srcImag, float parameter)
{
	//创建查表文件LUT
	unsigned char LUT[256];
	for (int i = 0; i < 256; i++)
	{
		//Gamma变换定义
		LUT[i] = saturate_cast<uchar>(pow((float)(i / 255.0), parameter)*255.0f);
	}
	Mat dstImage = srcImag.clone();
	//输入图像为单通道时,直接进行Gamma变换
	if (srcImag.channels() == 1)
	{
		MatIterator_<uchar>iterator = dstImage.begin<uchar>();
		MatIterator_<uchar>iteratorEnd = dstImage.end<uchar>();
		for (; iterator != iteratorEnd; iterator++)
			*iterator = LUT[(*iterator)];
	}
	else
	{
		//输入通道为3通道时,须要对每一个通道分别进行变换
		MatIterator_<Vec3b>iterator = dstImage.begin<Vec3b>();
		MatIterator_<Vec3b>iteratorEnd = dstImage.end<Vec3b>();
		//经过查表进行转换
		for (; iterator!=iteratorEnd; iterator++)
		{
			(*iterator)[0] = LUT[((*iterator)[0])];
			(*iterator)[1] = LUT[((*iterator)[1])];
			(*iterator)[2] = LUT[((*iterator)[2])];
		}
	}
	return dstImage;
}
 

示例:

#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>    
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/highgui/highgui_c.h>

#include <iostream>

using namespace cv;
using namespace std;

void MyGammaCorrection(Mat& src, Mat& dst, float fGamma)
{

	// build look up table  
	unsigned char lut[256];
	for (int i = 0; i < 256; i++)
	{
		lut[i] = saturate_cast<uchar>(pow((float)(i / 255.0), fGamma) * 255.0f);
	}

	dst = src.clone();
	const int channels = dst.channels();
	switch (channels)
	{
	case 1:   //灰度图的状况
	{

		MatIterator_<uchar> it, end;
		for (it = dst.begin<uchar>(), end = dst.end<uchar>(); it != end; it++)
			//*it = pow((float)(((*it))/255.0), fGamma) * 255.0;  
			*it = lut[(*it)];

		break;
	}
	case 3:  //彩色图的状况
	{

		MatIterator_<Vec3b> it, end;
		for (it = dst.begin<Vec3b>(), end = dst.end<Vec3b>(); it != end; it++)
		{
			//(*it)[0] = pow((float)(((*it)[0])/255.0), fGamma) * 255.0;  
			//(*it)[1] = pow((float)(((*it)[1])/255.0), fGamma) * 255.0;  
			//(*it)[2] = pow((float)(((*it)[2])/255.0), fGamma) * 255.0;  
			(*it)[0] = lut[((*it)[0])];
			(*it)[1] = lut[((*it)[1])];
			(*it)[2] = lut[((*it)[2])];
		}

		break;

	}
	}
}

int main()
{
	Mat image = imread("111.jpg");
	if (image.empty())
	{
		cout << "Error: Could not load image" << endl;
		return 0;
	}

	Mat dst;
	float fGamma = 1 / 2.2;
	MyGammaCorrection(image, dst, fGamma);

	imshow("Source Image", image);
	imshow("Dst", dst);

	waitKey();

	return 0;
}

 程序运行结果以下:

 

 

 

 

6  分段线性变换

   分段线性变换也是一种重要的灰度级变换。对于曝光不足,曝光过分和传感器动态范围都会形成图像表现出低对比度的特征。分段线性变换的做用是提升图像灰度级的动态范围。一般来讲,经过阶段必定比例的最亮像素和最暗像素,并使得中间亮度像素占有整个灰度级,于是可以提升图像的全局对比度。在这种状况下,一般称之为对比度拉伸,直方图裁剪,目前普遍的应用于图像后期处理中。一般使用分段函数来实现。

6.一、对比度拉伸技术

  图像的对比度拉伸是经过扩展图像灰度级动态范围来实现的,它能够扩展对应的所有灰度范围。图像的低对比度通常是因为图像图像成像亮度不够、成像元器件参数限制或设置不当形成的。提升图像的对比度能够加强图像各个区域的对比效果,对图像中感兴趣的区域进行加强,而对图像中不感兴趣的区域进行相应的抑制做用。对比度拉伸是图像加强中的重要的技术之一。这里设点(x1,y1)与(x2,y2)是分段线性函数中折点位置坐标。常见的三段式分段线性变换函数的公式以下:

    其中

  其图像以下:

 

    须要注意的是,分段线性通常要求函数是单调递增的,目的是防止图像中的灰度级不知足一一映射。

     分段的灰度拉伸技术能够结合直方图处理技术,从而更加灵活地控制输出图像的直方图分布,对特定感兴趣的区域进行对比度调整,加强图像画质。对于图像灰度集中在较暗的区域,能够采用斜率k<0来进行灰度拉伸扩展;对于图像中较亮的区域,能够采用修了k<0来进行灰度拉伸压缩。

示例:

#include "stdafx.h"
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("111.jpg", 0);
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		return -1;
	}
	imshow("原始图片", srcImage);
	Mat dstImage(srcImage);
	int rowsNum = dstImage.rows;
	int colsNum = dstImage.cols;
	//图像连续性判断
	if (dstImage.isContinuous())
	{
		colsNum = colsNum * rowsNum;
		rowsNum = 1;
	}
	//图像指针操做
	uchar *pDataMat;
	int pixMax = 0, pixMin = 255;
	//计算图像像素的最大值和最小值
	for (int j = 0; j < rowsNum; j++)
	{
		pDataMat = dstImage.ptr<uchar>(j);
		for (int i = 0; i < colsNum; i++)
		{
			if (pDataMat[i] > pixMax)
				pixMax = pDataMat[i];
			if (pDataMat[i] < pixMin)
				pixMin = pDataMat[i];
		}
	}

	//进行对比度拉伸
	for (int j = 0; j < rowsNum; j++)
	{
		pDataMat = dstImage.ptr<uchar>(j);
		for (int i = 0; i < colsNum; i++)
		{
			pDataMat[i] = (pDataMat[i] - pixMin) * 255 / (pixMax - pixMin);
		}
	}
	imshow("对比度拉伸后的图像", dstImage);
	waitKey();
	return 0;
}
 程序运行结果以下:

6.二、 灰度级分层

  灰度级分层的处理能够突出特定灰度范围的亮度,能够应用于加强某些特征。

  1. 将感兴趣范围内的全部灰度值显示位一个值(如白色),而将其余灰度值显示为另一个值(如黑色)。以下图左图所示,最后将产生一副二值图像
  2. 仅仅改变感兴趣范围的灰度值,使其显示为一个值,以下图右图所示。(这种方法通常用的少,这里就不详细介绍了)

 下面使用OpenCV对灰度级分层进行一个实现

示例:

 

#include "stdafx.h"
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("111.jpg", 0);
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		return 0;
	}
	imshow("原图像", srcImage);
	Mat dstImage = srcImage.clone();
	int rowsNum = dstImage.rows;
	int colsNum = dstImage.cols;
	//图像连续性判断
	if (dstImage.isContinuous())
	{
		colsNum *= rowsNum;
		rowsNum = 1;
	}
	//图像指针操做
	uchar *pDataMat;
	int controlMin = 50;
	int controlMax = 150;
	//计算图像的灰度级分层
	for (int j = 0; j < rowsNum; j++)
	{
		pDataMat = dstImage.ptr<uchar>(j);
		for (int i = 0; i < colsNum; i++)
		{
			//第一种方法,二值映射
			if (pDataMat[i] > controlMin)
				pDataMat[i] = 255;
			else
				pDataMat[i] = 0;
			//第二种方法:区域映射
			//if (pDataMat[i] > controlMax && pDataMat[j] < controlMin)
			//	pDataMat[i] = controlMax;
		}	
	}
	imshow("灰度分层后的图像", dstImage);
	waitKey();
	return 0;
}

运行结果以下所示:

 

6.二、比特平面分层

  像素是由比特组成的竖直。例如,在256级灰度图像中,每一个像素的灰度是由8bit组成,替代突出灰度级范围,咱们能够突出比特来突出整个图像的外观。一副8比特灰度图可考虑分层1到8个比特平面。很容易理解的是,4个高阶比特平面,特别是最后两个比特平面,包含了在视觉上很重要的大多数数据。而低阶比特平面则在图像上贡献了更精细的灰度细节。

示例:

#include "stdafx.h"
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int b[8];
void binary(int num)
{
	for (int i = 0; i < 8; i++)
		b[i] = 0;
	int i = 0;
	while (num != 0)
	{
		b[i] = num % 2;
		num = num / 2;
		i++;
	}
}

int main()
{
	Mat srcImage = imread("111.jpg", 0);
	resize(srcImage, srcImage, cv::Size(), 0.5, 0.5);
	Mat d[8];
	for (int k = 0; k < 8; k++)
		d[k].create(srcImage.size(), CV_8UC1);

	int rowNumber = srcImage.rows, colNumber = srcImage.cols;

	for (int i = 0; i < rowNumber; i++)
		for (int j = 0; j < colNumber; j++) {
			int num = srcImage.at<uchar>(i, j);
			binary(num);
			for (int k = 0; k < 8; k++)
				d[k].at<uchar>(i, j) = b[k] * 255;
		}
	imshow("src", srcImage);
	for (int k = 0; k < 8; k++) {
		imshow("level" + std::to_string(k), d[k]);
	}

	waitKey(0);
	return 0;
}

  

 

 

 

 

参考资料

OpenCV比特平面分层 C++

相关文章
相关标签/搜索