天天看點

Opencv4(C++)實戰案例1:将朦胧的圖像變清晰(去霧)CV_Assertsrc.convertTo(dst, type, scale, shift)copyMakeBorderreshapeMat_sortIdx通過測試代碼

本文的主要思想參考了以下文章,學習過程中對各個api進行了了解和分析。

原文

CV_Assert

檢查運作情況,如果出現錯誤,則顯示錯誤資訊。

在括号内填入想要檢測的資訊然後錯誤則抛出一個異常,否則就傳回true

在這裡就相當于我們做的一個image.empty()檢測。

src.convertTo(dst, type, scale, shift)

注意也不是所有格式的Mat型資料都能被使用儲存為圖檔,目前OpenCV主要隻支援單通道和3通道的圖像,并且此時要求其深度為8bit和16bit無符号(即CV_16U),是以其他一些資料類型是不支援的,比如說float型等。

convertTo()函數負責轉換資料類型不同的Mat,即可以将類似float型的Mat轉換到imwrite()函數能夠接受的類型。

而cvtColor()函數是負責轉換不同通道的Mat,因為該函數的第4個參數就可以設定目的Mat資料的通道數(隻是我們一般沒有用到它,一般情況下這個函數是用來進行色彩空間轉換的)。

縮放并轉換到另外一種資料類型:

dst:目的矩陣;

type:需要的輸出矩陣類型,或者更明确的,是輸出矩陣的深度,如果是負值(常用-1)則輸出矩陣和輸入矩陣類型相同;

scale:比例因子;

shift:将輸入數組元素按比例縮放後添加的值;

dst(i)=src(i)xscale+(shift,shift,…)

在去霧操作的第一步裡面就需要做一個轉化資料類型的操作

那麼在這裡我們需要知道一個知識點:

就是在浮點數的圖像資料類型中,其灰階值的範圍都是在0-1之間的,而原圖像是BGR的,所有像素點的灰階值都在0-255之間,是以如果要保持原圖像不變,要乘于一個比例因子:(float)(1/255)

copyMakeBorder

函數原型:

void copyMakeBorder( const Mat& src, Mat& dst,

int top, int bottom, int left, int right,

int borderType, const Scalar& value=Scalar() );

功能

擴充src的邊緣,将圖像變大,然後以各種外插方式自動填充圖像邊界,這個函數實際上調用了函數cv::borderInterpolate,這個函數最重要的功能就是為了處理邊界,比如均值濾波或者中值濾波中,使用copyMakeBorder将原圖稍微放大,然後我們就可以處理邊界的情況了

參數解釋

src,dst:原圖與目标圖像

top,bottom,left,right分别表示在原圖四周擴充邊緣的大小

borderType:擴充邊緣的類型,就是外插的類型,OpenCV中給出以下幾種方式

  • BORDER_REPLICATE
  • BORDER_REFLECT
  • BORDER_REFLECT_101
  • BORDER_WRAP
  • BORDER_CONSTANT

實際中,還有其他的宏定義

//! various border interpolation methods

enum { BORDER_REPLICATE=IPL_BORDER_REPLICATE, BORDER_CONSTANT=IPL_BORDER_CONSTANT,

BORDER_REFLECT=IPL_BORDER_REFLECT, BORDER_WRAP=IPL_BORDER_WRAP,

BORDER_REFLECT_101=IPL_BORDER_REFLECT_101, BORDER_REFLECT101=BORDER_REFLECT_101,

BORDER_TRANSPARENT=IPL_BORDER_TRANSPARENT,

BORDER_DEFAULT=BORDER_REFLECT_101, BORDER_ISOLATED=16 };

總而言之,在進行圖像卷積操作的時候為了處理圖像邊緣等像素的問題都是需要通過設定重複邊緣來達到我們想要的效果的

reshape

reshape有兩個參數:Mat::reshape(int cn, int rows=0)

其中,參數:cn為新的通道數,如果cn = 0,表示通道數不會改變。

參數rows為新的行數,如果rows = 0,表示行數不會改變。

注意:新的行列必須與原來的行列相等。就是說,如果原來是5行3列,新的行和列可以是1行15列,3行5列,5行3列,15行1列。僅此幾種,否則會報錯。

Mat_

在讀取矩陣元素時,以及擷取矩陣某行的位址時,需要指定資料類型。這樣首先需要不停地寫“”,讓人感覺很繁瑣,在繁瑣和煩躁中容易犯錯,如下面代碼中的錯誤,用 at()擷取矩陣元素時錯誤的使用了 double 類型。這種錯誤不是文法錯誤,是以在編譯時編譯器不會提醒。在程式運作時, at()函數擷取到的不是期望的(i,j)位置處的元素,資料已經越界,但是運作時也未必會報錯。這樣的錯誤使得你的程式忽而看上去正常,忽而彈出“段錯誤”,特别是在代碼規模很大時,難以查錯。

如果使用 Mat_類,那麼就可以在變量聲明時确定元素的類型, 通路元素時不再需要指定元素類型,即使得代碼簡潔,又減少了出錯的可能性。
           

sortIdx

在 MATLAB 裡,傳回排序後的矩陣以及對應原矩陣的索引是在 sort 一個函數搞定,但在 OpenCV 中,其功能分别被配置設定到了 cv::sort 和 cv::sortIdx 裡,cv::sort 負責傳回排序後的矩陣,cv::sortIdx 負責傳回對應原矩陣的索引。

在 OpenCV 中,我們用類似于 CV_SORT_EVERY_ROW + CV_SORT_ASCENDING 這樣的方式來一并指定對列還是對行以及升序還是降序,其訓示值定義如下,是以可以組合出 4 種不同的方式:

#define CV_SORT_EVERY_ROW    0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING    0
#define CV_SORT_DESCENDING   16
 
//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:對矩陣的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:對矩陣的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:對矩陣的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:對矩陣的每列按照降序排序;
————————————————
版權聲明:本文為CSDN部落客「JoannaJuanCV」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/zfjBIT/article/details/88397910
           

其他的就都沒有新知識點了,靠着論文去編碼就行

通過測試代碼

#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
#include <cstring>
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>

#define CV_SORT_EVERY_ROW    0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING    0
#define CV_SORT_DESCENDING   16

//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:對矩陣的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:對矩陣的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:對矩陣的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:對矩陣的每列按照降序排序;

using namespace std;
using namespace cv;

int main()
{
	Mat image = imread("E:/data/h1.jpg");
	if (image.empty())
	{
		cout << "error!";
		return 0;
	}
	cout << "complete" << endl;
	imshow("src", image);
	//imshow("src", src);
	//一、求暗通道
	//1.首先對圖像進行歸一化
	Mat fImage;
	image.convertTo(fImage, CV_32FC3, 1.0 / 255.0);//就是在浮點數的圖像資料類型中,其灰階值的範圍都是在0-1之間的,而原圖像是BGR的,所有像素點的灰階值都在0-255之間,是以如果要保持原圖像不變,要乘于一個比例因子:(float)(1/255)

	//2.設定運算視窗
	int hPatch = 15;
	int vPatch = 15;
	
	//3.給歸一化的圖檔設定邊界
	Mat fImageBorder;
	//此邊界是為了便于我們處理邊緣資料,在後續的卷積操作中,由于部分卷積算子的尺寸特殊性,有些邊緣像素沒辦法處理到,是以就要用此api進行邊緣的複制
	copyMakeBorder(fImage, fImageBorder, vPatch / 2, vPatch / 2, hPatch / 2, hPatch / 2, BORDER_REPLICATE);
	
	//4.分離通道
	std::vector<Mat> fImageBorderVector(3);
	split(fImageBorder,fImageBorderVector);//把三個通道push到vector中去

	//5.建立并且計算暗通道
	Mat darkChannel(image.rows,image.cols, CV_32FC1);//單通道圖像
	double minTemp, minPixel;
	//計算暗通道
	for (unsigned int r = 0; r < darkChannel.rows; r++)
	{
		for (unsigned int c = 0; c < darkChannel.cols; c++)
		{
			minPixel = 1.0;
			for (std::vector<Mat>::iterator it = fImageBorderVector.begin(); it != fImageBorderVector.end(); it++)
			{
				Mat roi(*it, Rect(c,r,hPatch,vPatch));
				minMaxLoc(roi,&minTemp);//相當于一個打擂算法,在這個視窗裡面找最小值
				minPixel = std::min(minPixel, minTemp);
			}
			//打擂結束,設定最小值,把暗通道算出來,并且指派
			darkChannel.at<float>(r, c) = float(minPixel);
		}
	}

	/*這一段代碼是用來測試我們的暗通道是否提取成功
	imshow("dst", darkChannel);
	Mat darkChannel8U;
	darkChannel.convertTo(darkChannel8U, CV_8UC1, 255, 0);
	imwrite("E:/outputdata/darkchannel_h1.jpg", darkChannel8U);
	success!
	*/

	//二、通過暗通道來實作A的過程,求的是大氣的那個值
	//1.計算出darkChannel中, 前top個亮的值, 論文中取值為0.1 %
	float top = 0.001;
	float numberTop = top * darkChannel.rows * darkChannel.cols;
	Mat darkChannelVectorOneRow = darkChannel.reshape(1, 1);//單通道,一行
	Mat_<int> darkChannelVectorIndex;
	//Mat_類
	sortIdx(darkChannelVectorOneRow, darkChannelVectorIndex, CV_SORT_EVERY_ROW + CV_SORT_DESCENDING);//擷取數組的索引
	//這個函數是用來先對原來的進行一次sort,然後傳回這個sort的數組的中每一個元素所對應元素數組的index,再形成一個arrary指派到第二個參數中去。
	//2.制作掩碼,設定一個二值mask
	Mat mask(darkChannelVectorIndex.rows, darkChannelVectorIndex.cols, CV_8UC1);//注意mask的類型必須是CV_8UC1
	//然後要做的是,找出原圖像中亮度在前百分之1的點,把對應的點設定為1,其他的都設定成0
	//然後由于每行是已經通過sortIdx序列化之後的結果
	for (unsigned int r = 0; r < darkChannelVectorIndex.rows; r++)
	{
		for (unsigned int c = 0; c < darkChannelVectorIndex.cols; c++)
		{
			if (darkChannelVectorIndex.at<int>(r, c) <= numberTop)
				mask.at<uchar>(r, c) = 1;
			else
				mask.at<uchar>(r, c) = 0;
		}
	}
	Mat darkChannelIndex = mask.reshape(1, darkChannel.rows);//單通道
	vector<double> A(3);//分别存取A_b,A_g,A_r
	vector<double>::iterator itA = A.begin();
	vector<Mat>::iterator it = fImageBorderVector.begin();
	//2.2在求第三步的t(x)時,會用到以下的矩陣,這裡可以提前求出
	vector<Mat> fImageBorderVectorA(3);
	vector<Mat>::iterator itAA = fImageBorderVectorA.begin();
	for (it= fImageBorderVector.begin() ; it != fImageBorderVector.end() && itA != A.end() && itAA != fImageBorderVectorA.end(); it++, itA++, itAA++)
	{
		Mat roi(*it, Rect(hPatch / 2, vPatch / 2, darkChannel.cols, darkChannel.rows));
		minMaxLoc(roi, 0, &(*itA), 0, 0, darkChannelIndex);//
		(*itAA) = (*it) / (*itA); //[注意:這個地方有除号,但是沒有判斷是否等于0]
	}

	/*第三步:求t(x)*/
	Mat darkChannelA(darkChannel.rows, darkChannel.cols, CV_32FC1);
	float omega = 0.95;//0<w<=1,論文中取值為0.95
	//代碼和求darkChannel的時候,代碼差不多
	for (unsigned int r = 0; r < darkChannel.rows; r++)
	{
		for (unsigned int c = 0; c < darkChannel.cols; c++)
		{
			minPixel = 1.0;
			for (itAA = fImageBorderVectorA.begin(); itAA != fImageBorderVectorA.end(); itAA++)
			{
				Mat roi(*itAA, Rect(c, r, hPatch, vPatch));
				minMaxLoc(roi, &minTemp);
				minPixel = min(minPixel, minTemp);
			}
			darkChannelA.at<float>(r, c) = float(minPixel);
		}
	}
	Mat tx = 1.0 - omega * darkChannelA;
	/*第四步:我們可以求J(x)*/
	float t0 = 0.1;//論文中取t0 = 0.1
	Mat jx(image.rows, image.cols, CV_32FC3);
	for (size_t r = 0; r < jx.rows; r++)
	{
		for (size_t c = 0; c < jx.cols; c++)
		{
			jx.at<Vec3f>(r, c) = Vec3f((fImage.at<Vec3f>(r, c)[0] - A[0]) / max(tx.at<float>(r, c), t0) + A[0], (fImage.at<Vec3f>(r, c)[1] - A[1]) / max(tx.at<float>(r, c), t0) + A[1], (fImage.at<Vec3f>(r, c)[2] - A[2]) / max(tx.at<float>(r, c), t0) + A[2]);
		}
	}
	namedWindow("jx", 1);
	imshow("jx", jx);
	Mat jx8u;
	jx.convertTo(jx8u, CV_8UC3, 255, 0);
	imwrite("E:/data-output/h1_res.jpg", jx8u);
	waitKey(0);
}
           

繼續閱讀