zoukankan      html  css  js  c++  java
  • C++ Opencv Mat类型使用的几个注意事项及自写函数实现Laplace图像锐化

    为了提升自己对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()显示时显示窗口就会一片黑暗。

     

    注:错误之处,敬请雅正!

  • 相关阅读:
    Datawhale编程实践(LeetCode 腾讯精选练习50)Task11
    Datawhale编程实践(LeetCode 腾讯精选练习50)Task10
    Datawhale编程实践(LeetCode 腾讯精选练习50)Task9
    Datawhale编程实践(LeetCode 腾讯精选练习50)Task8
    Datawhale编程实践(LeetCode 腾讯精选练习50)Task7
    Java多线程之三volatile与等待通知机制示例
    Java多线程之一
    [WC2021] 括号路径
    [CF1375H] Set Merging
    [CF1342E] Placing Rooks
  • 原文地址:https://www.cnblogs.com/HL-space/p/10546604.html
Copyright © 2011-2022 走看看