C++圖像處理學習(二)之圖像增強——五大灰度變換

感謝博客,轉自 https://blog.csdn.net/qq_34784753/article/details/61920138
1.灰度線性變換

圖像的灰度線性變換是圖像灰度變換的一種,圖像的灰度變換通過建立灰度映射來調整源圖像的灰度,從而達到圖像增強的目的。灰度映射通常是用灰度變換曲線來進行表示。通常來說,它是將圖像的像素值通過指定的線性函數進行變換,以此來增強或者來減弱圖像的灰度,灰度線性變換的函數就是常見的線性函數。

g(x, y) = k · f(x, y) + d

設源圖像的灰度值爲x,則進行灰度線性變換後的灰度值爲y = kx + b (0<=y<=255),下面分別來討論k的取值變化時線性變換的不同效果

(1).|k|>1時

當k>1時,可以用來增加圖像的對比度,圖像的像素值在進行變換後全部都線性方法,增強了整體的顯示效果,且經過這種變換後,圖像的整體對比度明顯增大,在灰度圖中的體現就是變換後的灰度圖明顯被拉伸了。

(2).|k|=1時

當k=1時,這種情況下常用來調節圖像的亮度,亮度的調節就是讓圖像的各個像素值都增加或是減少一定量。在這種情況下可以通過改變d值來達到增加或者是減少圖像亮度的目的。因爲當k=1,只改變d值時,只有圖像的亮度被改變了,d>0時,變換曲線整體發生上移,圖像的亮度增加,對應的直方圖整體向右側移動,d<0時,變換曲線整體下移,圖像的亮度降低,對應的直方圖發生水平左移。

(3).0<|k|<1時

此時變換的效果正好與k>1時相反,即圖像的整體對比度和效果都被削減了,對應的直方圖會被集中在一段區域上。k值越小,圖像的灰度分佈也就越窄,圖像看起來也就顯得越是灰暗。

(4).k<0時

在這種情況下,源圖像的灰度會發生反轉,也就是原圖像中較亮的區域會變暗,而較暗的區域將會變量。特別的,此時我們令k = -1,d = 255,可以令圖像實現完全反轉的效果。對應的直方圖也會發生相應的變化。

相應的程序試下如下:

//實現圖像的灰度線性變化

#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("1234.jpg");
	if (!srcImg.data)
	{
		cout << "讀入圖片失敗" << endl;
		return -1;
	}
	imshow("原圖像", srcImg);
	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 < 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);
 
			}
		}
	}
	imshow("線性變換後的圖像", dstImg);
	waitKey();
	return 0;
}

當k=1.2,b=50時 執行程序的效果如下:

2.灰度對數變換

對數變換的基本形式爲

其中,b是一個常數,用來控制曲線的彎曲程度,其中,b越小越靠近y軸,b越大越靠近x軸。表達式中的x是原始圖像中的像素值,y是變換後的像素值,可以分析出,當函數自變量較低時,曲線的斜率很大,而自變量較高時,曲線的斜率變得很小。正是因爲對數變換具有這種壓縮數據的性質,使得它能夠實現圖像灰度拓展和壓縮的功能。即對數變換可以拓展低灰度值而壓縮高灰度級值,讓圖像的灰度分佈更加符合人眼的視覺特徵。例如進行傅里葉變換後的圖像,圖像中心絕對高灰度值的存在壓縮了低灰度部分的動態範圍,所以無法在現實的時候便顯出原油的細節。這時就需要使用一個對數變換來對結果圖像進行修正,經過適當的處理後,原始圖像中低灰度區域的對比度將會增加,暗部細節將被增強。

使用程序進行實現如下:

//實現圖像的對數變換,作用是壓縮圖像較亮區域的動態範圍
//使用不同的方法實現圖像的對數變換
//基本公式爲 y = clog(1+r)
#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 srcImage = imread("1234.jpg",0);
	if (!srcImage.data)
	{
		cout << "讀入圖片錯誤~" << endl;
		return -1;
	}
	double c;
	cout << "請輸入常數c:";
	cin >> c;
	Mat srcImage1(srcImage);
	imshow("原圖像", srcImage);
	Mat dstImage1(srcImage.size(), srcImage.type());
	Mat dstImage2 = dstImage1.clone();
	Mat dstImage3 = dstImage1.clone();
	//使用第一種方法進行對數變換,對圖像整體進行操作
	//首先計算 1+r,注意,是對每一個像素點都進行加1操作
	add(srcImage, Scalar(1.0), srcImage1);
	//轉換爲32位的浮點數
	srcImage1.convertTo(srcImage1, CV_32F);
	//計算log(1+r)
	log(srcImage1, dstImage1);
	dstImage1 = c * dstImage1;
	//進行歸一化處理
	normalize(dstImage1, dstImage1, 0, 255, NORM_MINMAX);
	//convertScaleAbs:先縮放元素再取絕對值,最後轉換格式爲8bit型
	//在這裏不具有縮放功能,作用僅爲將格式轉換爲8bit型
	convertScaleAbs(dstImage1, dstImage1);
	imshow("對數變換圖像1", dstImage1);
	/////////////////////////////////////////////////////////////
	//使用第二種方法進行圖像的對數變換,對圖像的像素進行遍歷
	double temp = 0.0;
	for (int i = 0; i < srcImage.rows; i++)
	{
		for (int j = 0; j < srcImage.cols; j++)
		{
			temp = (double)srcImage.at<uchar>(i, j);
			temp = c*log((double)(1 + temp));
			dstImage2.at<uchar>(i, j) = saturate_cast<uchar>(temp);
		}
	}
	//進行歸一化處理
	normalize(dstImage2, dstImage2, 0, 255, NORM_MINMAX);
	convertScaleAbs(dstImage2, dstImage2);
	imshow("對數變換圖像2", dstImage2);
	//////////////////////////////////////////////////////////////
	//使用第三種方法進行圖像的對數變換
	//首先進行圖像類型轉換
	srcImage.convertTo(dstImage3, CV_32F);
	//圖像矩陣元素進行加1操作
	dstImage3 = dstImage3 + 1;
	//圖像對數操作
	cv::log(dstImage3, dstImage3);
	dstImage3 = c*dstImage3;
	//圖像進行歸一化操作
	normalize(dstImage3, dstImage3, 0, 255, NORM_MINMAX);
	convertScaleAbs(dstImage3, dstImage3);
	imshow("對數變換圖像3", dstImage3);
	
	waitKey();
	return 0;
}

當c取1時,效果如下:

3.灰度冪次變換與Gamma校正

基於冪次變換的Gamma校正是圖像處理中一種非常重要的非線性變換,它與對數變換相反,它是對輸入圖像的灰度值進行指數變換,進而校正亮度上的偏差。通常Gamma校正長應用於拓展暗調的細節。通常來講,當Gamma校正的值大於1時,圖像的高光部分被壓縮而暗調部分被擴展;當Gamma校正的值小於1時,相反的,圖像的高光部分被擴展而暗調備份被壓縮。

通常情況下,最簡單的Gamma校正可以用下面的冪函數來表示:

其中A是常數,函數的輸入和輸出都是非負數,當r=1時,爲直線變換;當r<1時,低灰度區域動態範圍擴大,進而圖像對比度增強,高灰度值區域動態範圍減小,圖像對比度降低,圖像整體灰度值增大,此時與圖像的對數變換類似。當r>11時,低灰度區域的動態範圍減小進而對比度降低,高灰度區域動態範圍擴大,圖像的對比度提升,圖像的整體灰度值變小,Gamma校正主要應用在圖像增強。目標檢測和圖像分析等不同的領域。

總之,r<1的冪函數的作用是提高圖像暗區域中的對比度,而降低亮區域的對比度;r>1的冪函數的作用是提高圖像中亮區域的對比度,降低圖像中按區域的對比度。

所以,對於灰度級整體偏暗的圖像,可以使用r<1的冪函數增大動態範圍。對於灰度級整體偏亮的圖像,可以使用r>1的冪函數增大灰度動態範圍。

下面使用程序進行簡單的Gamma變換:

//冪次變換與Gamma灰度校正

#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
 
using namespace std;
using namespace cv;
 
Mat GammaTrans(Mat &srcImag, float parameter);
 
 
int main()
{
	Mat srcImage = imread("1234.jpg", 0);
	if (!srcImage.data)
	{
		cout << "讀入圖片失敗!" << endl;
		return -1;
	}
	imshow("原始圖像", srcImage);
	//初始化幾組不同的參數
	float parameter1 = 0.3;
	float parameter2 = 3.0;
	Mat dstImage1 = GammaTrans(srcImage, parameter1);
	imshow("參數1下的Gamma變換", dstImage1);
	Mat dstImage2 = GammaTrans(srcImage, parameter2);
	imshow("參數2下的Gamma變換", dstImage2);
	waitKey();
	return 0;
}
 
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;
}

4.分段線性變換

分段線性變換也是一種重要的灰度級變換。對於曝光不足,曝光過度和傳感器動態範圍都會造成圖像表現出低對比度的特徵。分段線性變換的作用是提高圖像灰度級的動態範圍。通常來說,通過階段一定比例的最亮像素和最暗像素,並使得中間亮度像素佔有整個灰度級,因而能夠提高圖像的全局對比度。在這種情況下,通常稱之爲對比度拉伸,直方圖裁剪,目前廣泛的應用於圖像後期處理中。通常使用分段函數來實現。下面先簡單介紹一下對比度拉伸技術。

圖像的對比度拉伸是通過擴展圖像灰度級動態範圍來實現的,它可以擴展對應的全部灰度範圍。圖像的低對比度一般是由於圖像圖像成像亮度不夠、成像元器件參數限制或設置不當造成的。提高圖像的對比度可以增強圖像各個區域的對比效果,對圖像中感興趣的區域進行增強,而對圖像中不感興趣的區域進行相應的抑制作用。對比度拉伸是圖像增強中的重要的技術之一。這裏設點(x1,y1)與(x2,y2)是分段線性函數中折點位置座標。常見的三段式分段線性變換函數的公式如下:

其中k1=y1/x1,k2=(y2-y1)/(x2-x1),k3=(255-y2)/(255-y1)

需要注意的是,分段線性一般要求函數是單調遞增的,目的是防止圖像中的灰度級不滿足一一映射。

分段的灰度拉伸技術可以結合直方圖處理技術,從而更加靈活地控制輸出圖像的直方圖分佈,對特定感興趣的區域進行對比度調整,增強圖像畫質。對於圖像灰度集中在較暗的區域,可以採用斜率k<0來進行灰度拉伸擴展;對於圖像中較亮的區域,可以採用修了k<0來進行灰度拉伸壓縮。

實現代碼如下:

//實現對比度拉伸
#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("1234.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;
}

執行後顯示效果如下

5.灰度級分層

灰度級分層,也叫做灰度級切片,作用是在整個灰度級範圍內將設定窗口內的灰度和其他部分分開。從而突出圖像中具有一定灰度範圍的區域。大體上來說,灰度級分層有兩種類型,即:清除背景和保持背景。清除背景是將灰度窗口內的像素賦值爲較亮的值,而其他部分賦值爲較暗的值。經過這樣的處理後產生的是二值圖像,原圖像的細節將全部丟失。而保持背景指的是將灰度窗口內的像素賦值爲較亮的值,而其他部分的灰度保持不變。

相關代碼如下:

//實現灰度級分層
#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("2345.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;
}

灰度直方圖均衡化

#include<opencv2\opencv.hpp>
#include<cmath>
#include<iostream>
using namespace cv;
using namespace std;
Mat MyequalizeHist(Mat &srcImage)  
{
	int nRows = srcImage.rows;
	int nCols = srcImage.cols;
 
	int nSumPix[256];
	double nProDis[256];
	double nSumProDis[256];
	int EqualizeSumPix[256];
 
	for (int i = 0; i < 256; i++)
	{
		nSumPix[i] = 0;
		nProDis[i] = 0.0;
		nSumProDis[i] = 0.0;
		EqualizeSumPix[i] = 0;
	}
 
	for (int i = 0; i < nRows; i++)
	{
		for (int j = 0; j < nCols; j++)
		{
			nSumPix[(int)srcImage.at<uchar>(i, j)]++;
		}
	}
	
	
	for (int i = 0; i < 256; i++)
	{
		nProDis[i] = (double)nSumPix[i] / (nRows * nCols);
	}
	
	
	nSumProDis[0] = nProDis[0];
 
	
	for (int i = 1; i < 256; i++)
	{
		nSumProDis[i] = nSumProDis[i - 1] + nProDis[i];
	}
	
	
	for (int i = 0; i < 256; i++)
	{
		EqualizeSumPix[i] = cvRound((double)nSumProDis[i] * 255);
	}
	
	Mat resultImage(nRows, nCols, srcImage.type());
	for (int i = 0; i < nRows; i++)
	{
		
		for (int j = 0; j < nCols; j++)
		{
			
			resultImage.at<uchar>(i, j) = EqualizeSumPix[(int)srcImage.at<uchar>(i, j)];
		}
	}
	return resultImage;
 
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章