為了提升自己對Opencv中Mat數據類型的熟悉和掌握程度,自己嘗試着寫了一下Laplace圖像銳化函數,一路坎坷,踩坑不斷。現將代碼分享如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//Laplace濾波銳化圖像
void myLaplace(Mat Src, Mat Tem, Mat Dst)
{
int SrcH = Src.rows;
int SrcW = Src.cols;
int TemH = Tem.rows;
int TemW = Tem.cols;
//檢測模板行列是否為奇數
if (TemH * TemW % 2 == 0)
{
cerr << "模板規格錯誤" << endl;
return;
}
//用於存儲中間過程的計算結果。在進行濾波變換時,會有少量的行列遍歷不到,為避免未遍歷到的行列對結果的影響,因此將整個矩陣初始化為0,
Mat IntDst(SrcH, SrcW, CV_32SC1, Scalar(0));
//計算銳化后的掩模
char* pTem = (char*)Tem.data;//Mat.data默認指針類型為 uchar*,在不同應用場合下要進行相應的類型轉換
for (int i = 0; i < SrcH - 2; i++)
{ //Mat的各行都是連續存儲的,但行與行之間不一定定是連續的,最好用哪行就取出對應行的首地址
int* pSrc1 = Src.ptr<int>(i);
int* pSrc2 = Src.ptr<int>(i +1);
int* pSrc3 = Src.ptr<int>(i + 2);
int* pIntDst = IntDst.ptr<int>(i + 1);
for (int j = 0; j < SrcW - 2; j++)
{
//pSrc1[ j ]為當前模板作用鄰域左上角地址
pIntDst[ j + 1 ] = pSrc1[ j ] * pTem[ 0 ] + pSrc1[ j + 1 ] * pTem[ 1 ] + pSrc1[ j + 2 ] * pTem[ 2 ]\
+ pSrc2[ j ] * pTem[ 3 ] + pSrc2[ j + 1 ] * pTem[ 4 ] + pSrc2[ j + 2 ] * pTem[ 5 ]\
+ pSrc3[ j ] * pTem[ 6 ] + pSrc3[ j + 1 ] * pTem[ 7 ] + pSrc3[ j + 2 ] * pTem[ 8 ];
}
}
//將濾波處理后的信息加到原圖上
addWeighted(IntDst, 1, Src, 1, 0.0, IntDst);
//求最小值,將基准拉到0
double minNum, maxNum;
Point minLoc, maxLoc;
minMaxLoc(IntDst, &minNum, &maxNum, &minLoc, &maxLoc);
minNum = (int)minNum;
for (int i = 0; i < SrcH; i++)
{
int* pIntDst = IntDst.ptr<int>(i);
for (int j = 0; j < SrcW; j++)
{
pIntDst[ j ] -= minNum;
}
}
//求最大值,將整體范圍標定至0--255
double newMinNum, newMaxNum;
Point newMinLoc, newMaxLoc;
minMaxLoc(IntDst, &newMinNum, &newMaxNum, &newMinLoc, &newMaxLoc);
newMaxNum = (int)newMaxNum;
for (int i = 0; i < SrcH; i++)
{
int* pIntDst = IntDst.ptr<int>(i);
uchar* pDst = Dst.ptr<uchar>(i);
for (int j = 0; j < SrcW; j++)
{
pIntDst[ j ] = pIntDst[ j ] * 255 / newMaxNum;
pDst[ j ] = (uchar) pIntDst[ j ];
}
}
}
//將uchar型Mat矩陣寫入int型Mat矩陣(后續計算像素值會超過0--255范圍)
void UChar2Int(Mat inputMat, Mat outputMat)
{
for (int i = 0; i < inputMat.rows; i++)
{
uchar* pInputMat = inputMat.ptr<uchar>(i);
int* pOutputMat = outputMat.ptr<int>(i);
for (int j = 0; j < inputMat.cols; j++)
{
pOutputMat[j] = (int)pInputMat[j];
}
}
}
int main()
{
Mat mColorImage = imread("color.jpg");
Mat mImage = imread("color.jpg", 0);//讀取灰度圖
if (mColorImage.data == 0)
{
cerr << "彩圖讀取錯誤" << endl;
return -1;
}
if (mImage.data == 0)
{
cerr << "灰圖讀取錯誤" << endl;
return -1;
}
//創建3X3 Laplace算子
char templateArray[3][3] = { {-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1} };
Mat mTemplate(3, 3, CV_8SC1, templateArray);
//創建盛放輸入信息的Mat矩陣
Mat mIntImage(mImage.rows, mImage.cols, CV_32SC1, Scalar(0));
UChar2Int(mImage, mIntImage);
//創建盛放輸出信息的Mat矩陣(輸出灰度范圍在0--255間,一定要存儲在uchar中。若存放在int中,顯示時默認共有2的32次方個灰度級,0--255范圍過窄且靠近0,顯示黑屏)
Mat mOutputImage(mImage.rows, mImage.cols, CV_8UC1, Scalar(0));
//進行Laplace銳化並顯示
myLaplace(mIntImage, mTemplate, mOutputImage);
namedWindow("彩圖", WINDOW_NORMAL);
imshow("彩圖", mColorImage);
namedWindow("灰圖", WINDOW_NORMAL);
imshow("灰圖", mImage);
namedWindow("Laplace銳化", WINDOW_NORMAL);
imshow("Laplace銳化", mOutputImage);
waitKey();
destroyAllWindows();
return 0;
}
對圖像進行Laplace銳化時,最令人頭痛的就是數據類型的轉換了。眾所周知,一般的灰度圖256個灰度級,在Mat中存儲的數據類型都是uchar,即CV_8UC1,但進行線性運算后,矩陣中的部分數值會小於0,也有部分數值會大於255,就超出了uchar能表示的極限范圍。此時就要用int,即CV_32SC1, Mat矩陣數據在兩種類型之間轉換時麻煩且容易出錯。現將本次踩的坑與收獲經驗分享如下,若能助人,不勝榮幸:
1.在創建並初始化Mat時,發現了一種直接用數組初始化Mat矩陣的方法,前提是數組和矩陣大小相同且元素數據類型保持一致。
char templateArray[3][3] = { {-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1} };
Mat mTemplate(3, 3, CV_8SC1, templateArray);
2.用Mat.data獲取到的指針類型默認為uchar*型的,而與矩陣中元素的數據類型無關。使用時要注意指針類型的轉化。
3.灰度圖Mat矩陣中的元素多數是uchar(CV_8UC1)型的,有時需要訪問其中的單個元素(像素值)並用"cout<<"輸出。需要注意的是,用"cout<<"輸出char/uchar型數據時,輸出的並不是數字數據,而是數字對應的ASCII碼字符,若對應的字符不可打印,則顯示輸出為空。若要求輸出數字數據,可使用類型強制轉換后輸出(如:cout<<(int)num<<endl;)。
4.Mat的各行數據在內存中都是連續存儲的,但行與行之間的地址不一定連續。因此需要用哪行的數據,就最好先獲得對應行的首地址(uchar* p = image.ptr<uchar>(i),獲取第i行首地址)。(在一篇博客上看到的,真偽待考證,不過謹慎點總是好的)。
5.用imshow()顯示Mat矩陣存儲的圖像信息時,若元素的數據類型是uchar(CV_8UC1)的,就默認有256(2的8次方)個灰度級;若元素的數據類型是用int(CV_32SC1)的,就默認有2147483647 (2的32次方)個灰度級。普通灰度圖的灰度值都在0-255之間,在CV_8UC1下能夠正常顯示。要是將其數據類型轉化為CV_32SC1的,0-255的灰度值在2147483647的尺度下就顯得范圍過窄且無限靠近於0,用imshow()顯示時顯示窗口就會一片黑暗。
注:錯誤之處,敬請雅正!