1、矩阵存储格式
Mat的存储是逐行的存储的,matlab中是逐列存储的;
2、Mat的数据深度
很多OpenCV的函数支持的数据深度只有8位和32位的,所以要少使用CV_64F
Mat_<uchar>对应的是CV_8U,
Mat_<char>对应的是CV_8S,
Mat_<int>对应的是CV_32S,
Mat_<float>对应的是CV_32F,
Mat_<double>对应的是CV_64F,对应的数据深度如下:
? CV_8U - 8-bit unsigned integers ( 0..255 )
? CV_8S - 8-bit signed integers ( -128..127 )
? CV_16U - 16-bit unsigned integers ( 0..65535 )
? CV_16S - 16-bit signed integers ( -32768..32767 )
? CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
? CV_32F - 32-bit ?oating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
? CV_64F - 64-bit ?oating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
3、计算积分图的函数integral
C++: void integral(InputArray src, OutputArray sum, int sdepth=-1 )
C++: void integral(InputArray src, OutputArray sum, OutputArray sqsum, int sdepth=-1 )
C++: void integral(InputArray src, OutputArray sum, OutputArray sqsum, OutputArray tilted, int sdepth=-1 )
Parameters:
image – input image as W \times H, 8-bit or floating-point (32f or 64f).
sum – integral image as (W+1)\times (H+1) , 32-bit integer or floating-point (32f or 64f).
sqsum – integral image for squared pixel values; it is (W+1)\times (H+1), double-precision floating-point (64f) array.
tilted – integral for the image rotated by 45 degrees; it is (W+1)\times (H+1) array with the same data type as sum.
sdepth – desired depth of the integral and the tilted integral images, CV_32S, CV_32F, or CV_64F.
因为积分图计算之前会在左边界补一个列零和上边界补一行零,这是为了避免出现Image[-1][-1];故积分图输出的矩阵是(H+1)*(W+1)的,所以在创建存放积分图的Mat时要注意将原始长宽分别加1
4、图像边界处理
凡是涉及滤波(或卷积)的函数,都会有这样一个参数:
int borderType
因为两个矩阵卷积后尺寸会增大(详见
http://blog.csdn.net/kelvin_yan/article/details/39640757),在卷积前要根据卷积核的大小对原始图像进行扩充,扩充而来的像素,我们通过该参数来决定对这些像素填充什么值。
一些常用的函数里面都有这个参数:
GaussianBlur
Laplacian
Sobel
blur
copyMakeBorder
cornerHarris
等...
官方文档在Image Filtering栏目下有对borderType的说明:
/*
Various border types, image boundaries are denoted with '|'
* BORDER_REPLICATE: aaaaaa|abcdefgh|hhhhhhh
* BORDER_REFLECT: fedcba|abcdefgh|hgfedcb
* BORDER_REFLECT_101: gfedcb|abcdefgh|gfedcba
* BORDER_WRAP: cdefgh|abcdefgh|abcdefg
* BORDER_CONSTANT: iiiiii|abcdefgh|iiiiiii with some specified 'i'
*/
填充形式一目了然
5、convertTo方法
调用形式:
Mat src=imread('lena.jpg',CV_LOAD_IMAGE_GRAYSCALE);
src.convertTo(dst,CV_16U);
功能:将输入Mat转换为目标类型,并把结果放在输出Mat(也可以使输入Mat本身)。
注意:数据的取值范围,不能从大范围往小范围直接转换,例如,
Mat src=imread('lena.tiff',CV_LOAD_IMAGE_GRAYSCALE); //lena.jpg是16bit图像
src.convertTo(dst,CV_8U);
此时发生溢出,dst的数据均为255,imshow的结果就是一片白;
所以,如果想把16bit转8bit,必须通过线性灰度变换,可以用以下代码:
void normalizeImg(const Mat& src, Mat& dst, double range)
{
double maxval,minval;
minMaxLoc(src, &minval,&maxval);
double p = range/(maxval-minval+EPS);
dst = (src-minval)*p;
}
注意:convertTo方法属于矩阵的深拷贝,即为目标地址创建新的内存空间,如:
float a[100] = {1,2,0};
Mat src(10,10,CV_32F,a);
src.convertTo(src,CV_8U);
此时src的内存空间将发生改变,即执行convertTo前后,src.data指向不同的内存地址
6、imshow能够适应不同类型的输入数据
但若将8bit的数据赋值给CV_16U的Mat,用imshow将显示一片黑!
所以,如果想正常显示图像,要根据数据的灰度范围配合适当的存储类型,存储类型不是随便设置的!
7、图像位深度
在opencv的Mat.depth()中得到的是一个 0 – 6 的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;
8、行、列和高、宽
h == rows,w == cols
在一些函数中,形参的先后顺序为宽、高,如cv::Rect(int x, int y, int width, int height),cv::Size(int _width, int _height)
而在另一些函数中,顺序是反过来的,如cv::Mat::zeros(int rows, int cols, int type),cv::Mat::creat(int rows, int cols, int type)
基本有这样一个规律:几何概念上的东西(如长方形)就按照宽、高;而矩阵(最具代表性的莫过于Mat类)则按照行、列(矩阵中就没有高宽之说)
9、给矩阵的某个区域赋值
可以用与补零操作,如把1000*1000变成1024*1024
//input src is 1000*1000
Mat img;
img=Mat::zeros(cvSize(1024,1024),CV_16UC1);
src.copyTo(img(Rect(0,0,src.cols,src.rows)));
主要用到Rect提取ROI
10、用指针访问Mat元素时注意数据类型
如下代码:
double m[3][3] = {{2,3,4},{5,6,7},{8,9,10}};
Mat M(3,3,CV_64F,m);
const uchar* p = M.ptr<uchar>(1);
double v = p[2];
std::cout<<"\n"<<p[2]<<endl;
目的是想打印M的第2行第3个元素,结果显示0,因为ptr方法返回的指针是unsigned char的(一个字节),但是M的元素是double型的(8个字节),导致获得的数据并不完整
改正:
double m[3][3] = {{2,3,4},{5,6,7},{8,9,10}};
Mat M(3,3,CV_64F,m);
const double* p = M.ptr<double>(1);
double v = p[2];
std::cout<<"\n"<<p[2]<<endl;
11、使用at方法给Mat赋值前先初始化Mat的数据类型
如:
Mat img0(height,width, CV_8UC1);
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
img0.at<unsigned char>(i,j) = data[i][j];
}
unsigned char data[][]是原始数据放在数组内;如果不事先初始化img0的类型为CV_8UC1,则赋值失败
12、谨慎处理矩阵赋值!
就是Mat的深拷贝和浅拷贝问题:
赋值方式1:
dstImg = tempMat;
dstImg和tempMat为同一个变量,仅仅是名字不一样而已(变量的引用)!对任何一者的操作都会影响另一者。举个例子:
dstImg = tempMat;
add(tempMat,1,tempMat);
divide(dstImg,tempMat,dstImg);
本意是将dstImg./(dstImg+1),结果是dstImg./dstImg
这时候就要用clone方法创建矩阵了!
赋值方式2:
dstImg = tempMat.clone();
这样,dstImg和tempMat才是真正意义上的两个矩阵(各自独立占用一段内存)
13、找不到Size、Rect、Point的原始定义?
其实它们的真身就是Size_、Rect_、Point_,只不过opencv为了方便用户,用typedef去掉了那一横杠
在官方文档中有所描述,但容易忽略:
For your convenience, the following type aliases are defined:
typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
OpenCV defines the following Size_<> aliases:
typedef Size_<int> Size2i;
typedef Size2i Size;
typedef Size_<float> Size2f;
For your convenience, the Rect_<> alias is available:
typedef Rect_<int> Rect;
14、对图像处理前先转为浮点数类型
一般图像数据都是无符号整型数据,即CV_8U、CV_16U;如果处理结果会产生负数或者小数,例如差分、商运算,就要先用Mat::converTo()方法转换为CV_32F或CV_64F,而且要考虑是否需要创建一个原始数据的硬拷贝来存放,使用Mat::clone()方法,中间所有的处理都是对拷贝进行处理,这样就不会影响原始数据。
15、什么是in-place?
经常在refman里看到有些函数说支持in-place,其实就是说,一个函数有两个参数分别是输入图像指针和输出图像指针,当输入图像指针与输出图像指针是同一个指针的时候,就是in-place操作了。
如:
cvSmooth()函数支持in-place操作了
我们可以这么
cvSmooth(pImg,pImg);
而不必这么用
cvSmooth(pImgSrc,pImgDst);