【学习OpenCV】编程笔记:一些需要注意的细节

1、矩阵存储格式

Mat的存储是逐行的存储的,matlab中是逐列存储的;


2、Mat的数据深度

很多OpenCV的函数支持的数据深度只有8位和32位的,所以要少使用CV_64F

Mat_对应的是CV_8U,


Mat_对应的是CV_8S,


Mat_对应的是CV_32S,


Mat_对应的是CV_32F,


Mat_对应的是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(1);
	double v = p[2];
	std::cout<<"\n"<
目的是想打印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(1);
	double v = p[2];
	std::cout<<"\n"<


11、使用at方法给Mat赋值前先初始化Mat的数据类型

如:

	Mat img0(height,width, CV_8UC1);
	for(int i=0;i(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_ Point2i;
typedef Point2i Point;
typedef Point_ Point2f;
typedef Point_ Point2d;
OpenCV defines the following Size_<> aliases:

typedef Size_ Size2i;
typedef Size2i Size;
typedef Size_ Size2f;
For your convenience, the Rect_<> alias is available:
typedef Rect_ 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);


TO BE CONTINUE

你可能感兴趣的:(opencv)