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()显示时显示窗口就会一片黑暗。
注:错误之处,敬请雅正!
C++ Opencv Mat类型使用的几个注意事项及自写函数实现Laplace图像锐化的更多相关文章
- OpenCV Mat 类型定义和赋值
1.一般的Mat定义方法:cv::Mat M(height,width,<Type>),例: cv::Mat M(480,640,CV_8UC3); 表示定义了一个480行640列的矩阵, ...
- 在MFC中显示图片(opencv Mat类型)
1,在MFC窗体中添加picture control控件,并添加对应的变量名 2,在窗体的初始化窗口中添加: namedWindow(); HWND hWnd = (HWND)cvGetWindowH ...
- opencv中的Mat类型
Mat类型主要是跟matlab中的数据类型一样.故用起来很方便. Mat最大的优势跟STL很相似,都是对内存进行动态的管理,不需要之前用户手动的管理内存,对于一些大型的开发,有时候投入的lpImage ...
- opencv中Mat类型数据操作与遍历
Mat作为opencv中一种数据类型常常用来存储图像,相对与以前的IplImgae类型来说,Mat类型省去了人工的对内存的分配与释放,转而自动分配释放.Mat Class主要包括两部个数据部分:一个是 ...
- OpenCV不同类型Mat的at方法访问元素时该如何确定模板函数的typename(转)
自从OpenCV推出了Mat后越来越像是Matlab了,使用起来方便了很多,但是,在用at方法访问Mat时,如何选用合适的typename类型来访问相应的Mat元素是个头疼的问题. 比如: int H ...
- OpenCV——Mat、CvMat、IplImage类型浅析【转】
OpenCV中常见的与图像操作有关的数据容器有Mat,cvMat和IplImage. 一.Mat类型:矩阵类型,Matrix. 在openCV中,Mat是一个多维的密集数据数组.可以用来处理向量和矩阵 ...
- Opencv Mat的三种常用类型简介
本系列文章由 @YhL_Leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/47683127 本文主要介绍Opencv ...
- OpenCV中图像以Mat类型保存时各通道数据在内存中的组织形式及python代码访问各通道数据的简要方式
以最简单的4 x 5三通道图像为例,其在内存中Mat类型的数据组织形式如下: 每一行的每一列像素的三个通道数据组成一个一维数组,一行像素组成一个二维数组,整幅图像组成一个三维数组,即: Mat.dat ...
- 网络流中的图像转化为OpenCV中的Mat类型
1,从网络中读取到的图像流,不支持查找,不能直接转化为Mat类型 2,例子如下: string Url = "http://192.168.0.110/cgi-bin/camera?reso ...
随机推荐
- MSSQL死锁进程查看及关闭
select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tran ...
- js对象属性 通过点(.) 和 方括号([]) 的不同之处
// js对象属性 通过点(.) 和 方括号([]) 的不同之处 // 1.点操作符: 静态的.右侧必须是一个以属性名称命名的简单标识符.属性名用一个标识符来表示.标识符必须直接出现再js ...
- 第一二次java实训作业
1. 声明一个整型变量a,并赋初值5,在程序中判断a是奇数还是偶数,然后输出判断的结果. package java1; public class java1 { static int a=5; pub ...
- Chrome自定义滚动条
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ ::-webkit-scrollbar { width: 16px; height: 16px; background-color: #F5 ...
- HttpWebRequest.AddRange 支持long类型
很久很久以前,在哪个FAT32格式还流行的年代,文件大小普遍还没超过4G的年代,.Net已经出来了. 而那时候.Net实现的HTTP断点续传协议,还没预料到如此普及(我猜的).那时候的HttpWebR ...
- XBee 802.15.4/Digimesh FAQs:如何为2.4G模块选择合适的信道
XBee 802.15.4模块和XBee Digimesh模块在硬件上完全相同,只是出厂带有不同固件,如果测试需要,这两个固件可以都可以互换烧入模块中. 如何为2.4G模块选择合适的信道 IEEE 8 ...
- IntelliJ IDEA 控制台中文乱码解决方案
配置Intellij的配置文件(在idea安装目录bin目录下) 打开Intellij的根目录,找到下图的两个文件(根据你的系统是32位或64位选择其中一个配置文件),在配置文件中添加: -Dfile ...
- kvm+webvirtmgr在centos7上的部署
#!/bin/bash #+++++++++++++++++++++++++++++++++++++++++++++++++++++++安装配置kvm并创建虚拟机+++++++++++++++++++ ...
- golang使用 gzip压缩
golang使用 gzip压缩 这个例子中使用gzip压缩格式,标准库还支持zlib, bz2, flate, lzw 压缩处理_三步: 1.创建压缩文件2.gzip write包装3.写入数据 ou ...
- redux之applyMiddleware
redux之所以伟大就在于中间件了,中间件为redux提供了无限可能.redux中中间件是一个不太容易理解的概念,因为涉及到compose.hoc等函数式的概念,看源代码总是懵懵的感觉.今天我们就来详 ...