为了提升自己对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()显示时显示窗口就会一片黑暗。
注:错误之处,敬请雅正!