十一、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++

相關文章
相關標籤/搜索