opencv 图像像素为图像的每一个像素点赋值,出现如下错误是什么原因?

OpenCV的基础与使用(65)
有时候,我们在看OpenCV源码的时候,需要一幅非常简单的图像来测试(图像简单咱们可以做人工推算,然后与机器运算的结果对照),这个时候就需要用OpenCV建立一幅只有几个像素且值可以自定义的图像,并保存到硬盘。为什么要保存到硬盘呢?因为咱们还要在MATLAB中做验证啊!为啥不用MATLAB生成呢?原因可见我写的博文:
http://blog.csdn.net/wenhao_ir/article/details/
首先用WIN7的画图功能建立一幅指定像素点数的图像,保存为XXX.jpg,然后放入相关工程文件下,接下来在OpenCV中建立如下关键代码:
PS:下面的这个代码只是C代码,我于日在此博文中补充了C++版本的代码!
#include &opencv2/opencv.hpp&
#include &opencv2/legacy/compat.hpp&
#include &fstream&
#pragma comment(linker, &/subsystem:\&windows\& /entry:\&mainCRTStartup\&&)
int main()
// 从文件中加载原图
IplImage *pSrcImage = cvLoadImage(&pout1.jpg&, CV_LOAD_IMAGE_UNCHANGED);
//创建输出的图像
IplImage *pOutImage_8U_1 = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U,1);
IplImage *g_pGrayImage =
cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
cvConvertScale(g_pGrayImage,pOutImage_8U_1);//相当于复制图像
CvScalar s2;
s2.val[0]=50;
cvSet2D(pOutImage_8U_1,0,0,s2);
s2.val[0]=200;
cvSet2D(pOutImage_8U_1,0,1,s2);
s2.val[0]=220;
cvSet2D(pOutImage_8U_1,0,2,s2);
s2.val[0]=100;
cvSet2D(pOutImage_8U_1,0,3,s2);
s2.val[0]=200;
cvSet2D(pOutImage_8U_1,0,4,s2);
cvSaveImage(&1234.jpg&,pOutImage_8U_1);
double watch_pOutImage_8U_1[100];
for(i=0;i& 5;i++)
watch_pOutImage_8U_1[i]=cvGet2D(pOutImage_8U_1,0,i).val[0];
cvSaveImage(&pOutImage_8U_1.jpg&,pOutImage_8U_1);
//这里记得释放相关资源,由于不是正规程序,这里就不写资源的释放了哈
运行完成之后在工程文件夹下就生成了pOutImage_8U_1.jpg,用MATLAB读入这幅图像,其值也为[50 200 220 100 200]
日补充C++代码:
在C++中cvSaveImage换成了imwrite,原型如下:
C++: bool imwrite(const string& filename, InputArray image, const vector&int&& params=vector&int&())
C: int cvSaveImage(const char* filename, const CvArr* image)
可见,imwrite比cvSaveImage多了一个参数,下面说下这个参数:
参数params是用来设置对应图片格式的参数的,因为一般情况下这些图片格式都是经过了压缩的,这里就是设置这些压缩参数来控制图片的质量。该参数是一个vector&int&类型,里面分别存入paramId_1, paramValue_1, paramId_2, paramValue_2, ... 也就是说存入一对属性值。如果不设置该参数的话,则程序会自动根据所保存的图像格式采用一个默认的参数。
下面给出我实际使用中的代码:
cv::imwrite(&flower3_pepper.jpg&,resultImage);//其中resultImage是MAT类型!
PS:MATLAB中也是使用imwrite来保存图像哦!OpenCV是在向MATLAB靠拢?
欢迎大家加入图像识别技术交流群:,另外,特别欢迎成都从事图像识别工作的朋友交流,我的QQ号
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:171428次
积分:3041
积分:3041
排名:第10171名
原创:221篇
评论:45条
(29)(43)(49)(51)(39)(15)struct数据结构processinglibrarywindowsimage目录(?)[+]转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7557063!!此篇是基于IplImage* (C接口或者说2.1之前版本的接口,新的Mat的访问方式请参考博文:《访问Mat图像中每个像素的值》)IplImage是OpenCV中CxCore部分基础的数据结构,用来表示图像,其中Ipl是Intel Image Processing Library的简写。以下是IplImage的结构分析(来自OpenCV中文网站:http://www./index.php/Cxcore%E5%9F%BA%E7%A1%80%E7%BB%93%E6%9E%84#IplImage)[cpp]&view plaincopytypedef&struct&_IplImage&&&&&&{&&&&&&&&&&int&&nS&&&&&&&&&/*&IplImage大小&*/&&&&&&&&&&int&&ID;&&&&&&&&&&&&/*&版本&(=0)*/&&&&&&&&&&int&&nC&&&&&/*&大多数OPENCV函数支持1,2,3&或&4&个通道&*/&&&&&&&&&&int&&alphaC&&/*&被OpenCV忽略&*/&&&&&&&&&&int&&&&&&&&&&&/*&像素的位深度:&IPL_DEPTH_8U,&IPL_DEPTH_8S,&IPL_DEPTH_16U,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&IPL_DEPTH_16S,&IPL_DEPTH_32S,&IPL_DEPTH_32F&and&IPL_DEPTH_64F&可支持&*/&&&&&&&&&&char&colorModel[4];&/*&被OpenCV忽略&*/&&&&&&&&&&char&channelSeq[4];&/*&同上&*/&&&&&&&&&&int&&dataO&&&&&/*&0&-&交叉存取颜色通道,&1&-&分开的颜色通道.&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cvCreateImage只能创建交叉存取图像&*/&&&&&&&&&&int&&&&&&&&&&/*&0&-&顶—左结构,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&1&-&底—左结构&(Windows&bitmaps&风格)&*/&&&&&&&&&&int&&&&&&&&&&&/*&图像行排列&(4&or&8).&OpenCV&忽略它,使用&widthStep&代替&*/&&&&&&&&&&int&&&&&&&&&&&/*&图像宽像素数&*/&&&&&&&&&&int&&&&&&&&&&/*&图像高像素数*/&&&&&&&&&&struct&_IplROI&*/*&图像感兴趣区域.&当该值非空只对该区域进行处理&*/&&&&&&&&&&struct&_IplImage&*maskROI;&/*&在&OpenCV中必须置NULL&*/&&&&&&&&&&void&&*imageId;&&&&&/*&同上*/&&&&&&&&&&struct&_IplTileInfo&*tileI&/*同上*/&&&&&&&&&&int&&imageS&&&&&/*&图像数据大小(在交叉存取格式下imageSize=image-&height*image-&widthStep),单位字节*/&&&&&&&&&&char&*imageD&&/*&指向排列的图像数据&*/&&&&&&&&&&int&&widthS&&&/*&排列的图像行大小,以字节为单位&*/&&&&&&&&&&int&&BorderMode[4];&/*&边际结束模式,&被OpenCV忽略&*/&&&&&&&&&&int&&BorderConst[4];&/*&同上&*/&&&&&&&&&&char&*imageDataO&/*&指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的&*/&&&&&&}&&&&&&IplI&&直接访问:对我们来说比较重要的两个元素是:char *imageData以及widthStep。imageData存放图像像素数据,而widStep类似CvMat中的step,表示以字节为单位的行数据长度。一个m*n的单通道字节型图像,其imageData排列如下:如果我们要遍历图像中的元素,只需:[cpp]&view plaincopyIplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);&&uchar*&&&for(int&i=0;i&img-&i++)&&&&&&for(int&j=0;j&img-&j++)&&&&&&&&&&*tmp=((uchar&*)(img-&imageData&+&i*img-&widthStep))[j];&&这种直接访问的方法速度快,但容易出错,我们可以通过定义指针来访问。即:[cpp]&view plaincopyIplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);&&ucha*&data=(uchar&*)img-&imageD&&int&step&=&img-&widthStep/sizeof(uchar);&&uchar*&&&for(int&i=0;i&img-&i++)&&&&&&for(int&j=0;j&img-&j++)&&&&&&&&&&*tmp=data[i*step+j];&&而多通道(三通道)字节图像中,imageData排列如下:其中(Bi,Bj)(Gi,Gj)(Ri,Rj)表示图像(i,j)处BGR分量的值。使用指针的遍历方法如下:[cpp]&view plaincopyIplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);&&IplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);&&uchar*&data=(uchar&*)img-&imageD&&int&step&=&img-&widthStep/sizeof(uchar);&&int&channels&=&img-&nC&&uchar&*b,*g,*r;&&for(int&i=0;i&img-&i++)&&&&&&&for(int&j=0;j&img-&j++){&&&&&&&&&&&&&*b=data[i*step+j*chanels+0];&&&&&&&&&&&&&*g=data[i*step+j*chanels+1];&&&&&&&&&&&&&*r=data[i*step+j*chanels+2];&&&&&&&&}&&*如果要修改某像素值,则直接赋值。使用cvGet2D()函数访问:cvGet*D系列函数可以用来返回特定位置的数组元素(一般使用cvGet2D),原型如下:[cpp]&view plaincopyCvScalar&cvGet1D(&const&CvArr*&arr,&int&idx0&);&&CvScalar&cvGet2D(&const&CvArr*&arr,&int&idx0,&int&idx1&);&&CvScalar&cvGet3D(&const&CvArr*&arr,&int&idx0,&int&idx1,&int&idx2&);&&CvScalar&cvGetND(&const&CvArr*&arr,&int*&idx&);&&idx0,idx1,idx2分别用来指示元素数组下标,即cvGet2D返回(idx0,idx1)处元素的值。因此,单通道图像像素访问方式如下:[cpp]&view plaincopyIplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);&&double&&&for(int&i=0;i&img-&i++)&&&&&&for(int&j=0;j&img-&j++)&&&&&&&&&&tmp=cvGet2D(img,i,j).val[0];&&多通道字节型/浮点型图像:[cpp]&view plaincopyIplImage*&img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);&&double&tmpb,tmpg,&&for(int&i=0;i&img-&i++)&&&&&&for(int&j=0;j&img-&j++){&&&&&&&&&&tmpb=cvGet2D(img,i,j).val[0];&&&&&&&&&&tmpg=cvGet2D(img,i,j).val[1];&&&&&&&&&&tmpr=cvGet2D(img,i,j).val[2];&&&&&&}&&如果是修改元素的值,可用cvSet*D(一般是cvSet2D)函数:[cpp]&view plaincopyvoid&cvSet1D(&CvArr*&arr,&int&idx0,&CvScalar&value&);&&void&cvSet2D(&CvArr*&arr,&int&idx0,&int&idx1,&CvScalar&value&);&&void&cvSet3D(&CvArr*&arr,&int&idx0,&int&idx1,&int&idx2,&CvScalar&value&);&&void&cvSetND(&CvArr*&arr,&int*&idx,&CvScalar&value&);&&这种方法对于任何图像的访问方式是一样的,比较简单,但效率较低,不推荐使用。版权声明:本文为博主原创文章,未经博主允许不得转载。上一篇【大学生活】幸运的是还有时间下一篇【OpenCV】分离多通道图像RGB的值顶56踩0主题推荐colorc语言opencv猜你在找Windows Server 2012 DHCP Server 管理Windows CE车载应用的实现与相关技术点数据结构(C版)SCOM 2012 SP1 管理Qt基础与Qt on Android入门查看评论
最新教程周点击榜
微信扫一扫本帖子已过去太久远了,不再提供回复功能。1451人阅读
OPENCV(1)
参考资料:
《OpenCV 2 Computer Vision Application Programming Cookbook》《The OpenCV Reference Manual》《Learning OpenCV》
Mat img = imread(filename)
如果读入的是 jpg 格式的图片,默认会读入三个通道的数据。如果需要当做灰度图像读入,使用:
Mat img = imread(filename, 0);
也可以先读入再转换成灰度图:
Mat img = imread(&image.jpg&);McvtColor(img, grey, CV_BGR2GRAY);
imwrite(filename, img);
展示一幅 8U 图像
Mat img = imread(&image.jpg&);namedWindow(&image&, CV_WINDOW_AUTOSIZE);imshow(&image&, img);waitKey();
展示一幅 32F 的图像
需要先转成 8U 类型。例如:
123456789101112131415
Mat img = imread(&image.jpg&);McvtColor(img, grey, CV_BGR2GRAY);MSobel(grey, sobelx, CV_32F, 1, 0);double minVal, maxVminMaxLoc(sobelx, &minVal, &maxVal); Msobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));namedWindow(&image&, CV_WINDOW_AUTOSIZE);imshow(&image&, draw);waitKey();
要获取 Mat 容器里的像素值,例如一幅图像里某个像素的亮度值,首先要求你得了解这幅图像的类型和通道数。
灰度图像访问单像素值
获取单通道灰度图(类型为8UC1)里像素点 (x,y)(x,y)
的亮度值:
Scalar intensity = img.at&uchar&(y,x);
也可以这么写:
Scalar intensity = img.at&uchar&(Point(x, y));
得到的 intensity.val[0] 将包含一个从 0~255 之间的数值。
彩色图像访问单像素值
对于 3 通道的 BGR 彩色图像,可以这么写:
Vec3b intensity = img.at&Vec3b&(y,x);uchar blue = intensity.val[0];uchar green = intensity.val[1];uchar red = intensity.val[2];
浮点型的图像也以此类推,注意使用浮点型的变量保存即可。
遍历所有像素
如果要遍历所有像素,可以使用 C 语言的方式,先从数组第一行开始,遍历每一行。cv::Mat 类提供了一个访问图像一行的地址方法:ptr 函数,该函数为一个模板函数。
1234567891011121314151617181920
* colorReduce - reduce color number * * @param image - the image for processing * @param div - reduce factor */void colorReduce(cv::Mat &image, int div=64){
int nl = image.
int nc = image.cols * image.channels();
for (int j=0; j& ++j) {
uchar *data = image.ptr&uchar&(j);
for (int i=0; i& ++i) {
data[i] = data[i] / div * div + div / 2;
在系统底层,为了方便硬件解码,一幅二维图像可能会在每一行的末尾填补一个额外的像素,这个额外填补的像素不会被显示或储存,且它们所存储的值会被忽略,它们起到一个哨兵的作用。
但对于没有使用额外像素填补的图像,图像中的每个像素都是实际像素,因此可以把整幅图像直接当做一维数组来遍历每个元素,从而减轻了循环的开销。cv::Mat 类提供了
isContinuous 函数来检测是否属于这种情况。
1234567891011121314151617181920212223242526
* colorReduce - reduce color number * * @param image - the image for processing * @param div - reduce factor */void colorReduce(cv::Mat &image, int div=64){
int nl = image.
int nc = image.cols * image.channels();
if (image.isContinuous()) {
for (int j=0; j& ++j) {
uchar *data = image.ptr&uchar&(j);
for (int i=0; i& ++i) {
data[i] = data[i] / div * div + div / 2;
另一种遍历像素的方法是使用 STL 风格的迭代器,如 cv::MatIterator_ 和 cv::MatConstIterator_:
cv::MatIterator_&cv::Vec3b&
也可以使用 iterator 类型,在 Mat_ 模板类里定义:
cv::Mat_&cv::Vec3b&::
123456789101112131415161718
* colorReduce - reduce color number * * @param image - the image for processing * @param div - reduce factor */void colorReduce(cv::Mat &image, int div=64){
cv::MatIterator_&cv::Vec3b& it = image.begin&cv::Vec3b&();
cv::MatIterator_&cv::Vec3b& itend = image.end&cv::Vec3b&();
for ( ; it!= ++it) {
(*it)[0] = (*it)[0] / div * div + div / 2;
(*it)[1] = (*it)[1] / div * div + div / 2;
(*it)[2] = (*it)[2] / div * div + div / 2;
Mat 的迭代器是一个,因此支持完整的迭代器算术运算,如
std::sort() 等。
遍历并访问相邻像素
有时候需要在遍历图像的同时访问相邻的像素。例如,用于进行边缘增强的拉普拉斯算子的表达式为:
增强后的像素值 = 5*当前 - 左 - 右 - 上 - 下
可使用三个指针来进行图像遍历,一个用于当前行,一个用于上面一行,一个用于下面一行:
1234567891011121314151617181920212223242526272829303132
* sharpen - lapracian sharpen function * * @param image
- the source grey scale image * @param result - the output grey scale image */void sharpen(const cv::Mat &image, cv::Mat &result){
result.create(image.size(), image.type());
for (int j=1; j&image.rows-1; ++j) {
const uchar *previous =
image.ptr&const uchar&(j-1);
const uchar *current =
image.ptr&const uchar&(j);
const uchar *next
image.ptr&const uchar&(j+1);
uchar *output = result.ptr&uchar&(j);
for (int i=1; i&image.cols-1; ++i) {
*output++ = cv::saturate_cast&uchar&(5*current[i]-current[i-1]
-current[i+1]-previous[i]-next[i]);
result.row(0).setTo(cv::Scalar(0));
result.row(result.rows-1).setTo(cv::Scalar(0));
result.col(0).setTo(cv::Scalar(0));
result.col(result.cols-1).setTo(cv::Scalar(0));}
可以使用 cv::split 操作来将彩色图像分离成三个单通道图像,使用 cv::merge 操作可以重新将几个单通道图像合并成一个多通道图像。下面的程序演示了将一幅图像 image2 与另一幅图像 image1 的蓝色通道混合:
std::vector&cv::Mat&cv::split(image1, planes);planes[0] += image2;cv::merge(planes, result);
简单图像运算
cv::add(imageA, imageB, resultC);cv::add(imageA, cv::Scalar(k), resultC);
cv::addWeighted(imageA, k1, imageB, k2, k3, resultC);
// c[i]= k*a[1]+b[i];cv::scaleAdd(imageA, k, imageB, resultC);
带掩码叠加
cv::add(imageA, imageB, resultC, mask);
当使用 mask 时,该操作只作用在对应的掩码位置不为 0 的像素上(mask 必须为单通道)。
其他常用的操作,包括:
cv::substract:两个图像相减,支持 mask;cv::absdiff:两个图像的差的绝对值,支持 mask;cv::multiply:两个图像逐元素相乘,支持 mask;cv::divide:两个图像逐元素相除,支持 mask;按位操作 cv::bitwise_and、cv::bitwise_or、cv::bitwise_xor、cv::bitwise_not;cv::max 和 cv::min :求每个元素的最小值或最大值返回这个矩阵,并返回结果矩阵。cv::saturate_cast:确保值不会超出像素的取值范围(防止上溢和下溢)。
这些图像操作都要求参与运算的两幅图像大小相同。如果不符合这种情况,可以使用
。另外,因为这些运算都是逐元素进行的,因此可以在调用时直接把其中一张图像的变量直接作为输出变量。
更多的操作可以参考
感兴趣区域(ROI)
下面的程序演示了将一幅图像叠加到另一幅图像的一个感兴趣区域中。
cv::Mat imageROI;imageROI= image(cv::Rect(385,270,logo.cols,logo.rows));cv::addWeighted(imageROI,1.0,logo,0.3,0.,imageROI);
叠加结果图
OpenCV 提供了一个cv::resize() 函数,允许你指定新的图像大小,例如:
cv::Mat resizedI cv::resize(image, resizedImage,
cv::Size(image.cols/3, image.rows/3));
查找表是一种映射,可以将图像原来的像素的灰度值根据查找表指定的规则映射到另一个值。OpenCV 提供了 cv::LUT 来支持这种变换。
下面示例一个将图像反色的查找表变换:
123456789101112131415161718
cv::Mat inverseColor(const cv::Mat &image) {
int dim(256);
cv::Mat lut(1,
for (int i=0; i&256; ++i)
lut.at&uchar&(i) = 255-i;
cv::LUT(image, lut, result);
反色结果图
阈值处理
阈值处理可以用来从图像中剔除低于或高于一定值的像素,其基本的思想是,给定一个数组和一个阈值,然后根据数组中的每个元素的值是低于还是高于阈值而进行一些处理。OpenCV 提供了
cv::threshold() 操作来进行阈值处理:
double threshold(InputArray src,
OutputArray dst,
double thresh,
double maxval,
其中,阈值类型选项 type 可以是以下几种类型:
cv::THRESH_BINARY
二值阈值化
dsti=(srci&T)?M:0dsti=(srci&T)?M:0
cv::THRESH_BINARY_INV
反向二值阈值化
dsti=(srci&T)?0:Mdsti=(srci&T)?0:M
cv::THRESH_TRUNC
截断阈值化
dsti=(srci&T)?M:srcidsti=(srci&T)?M:srci
cv::THRESH_TOZERO
超过阈值被置于0
dsti=(srci&T)?srci:0dsti=(srci&T)?srci:0
cv::THRESH_TOZERO_INV
低于阈值被置于0
dsti=(srci&T)?0:srcidsti=(srci&T)?0:srci
各种阈值类型的操作结果可以参考下图:
将被阈值化的值和阈值
二值阈值化
反向二值阈值化
截断阈值化
超过阈值被置于0
低于阈值被置于0
cv::Mcv::threshold(image,thresholded,60,255,cv::THRESH_BINARY);
形态学变换
cv::Mat element(7,7,CV_8U,cv::Scalar(1));cv::erode( image, result, element );
上面的 element 是结构元素,在这里用到了矩形结构元素。OpenCV 提供了几种形状的结构元素,可以通过 cv::getStructuringElement() 来定义:
Mat getStructuringElement(int shape, Size ksize, Point anchor=Point(-1,-1))
其中,shape 包含几种形状:
MORPH_Rect - 矩形结构元素;MORPH_Ellipse - 椭圆形结构元素;MORPH_CROSS - 十字形结构元素。
也可以自己定义一个形状,例如定义一个 “X” 形结构元素:
cv::Mat x(5,5,CV_8U,cv::Scalar(0));for (int i=0; i&5; i++) {
x.at&uchar&(i,i)= 1;
x.at&uchar&(4-i,i)= 1;}
cv::Mat element(7,7,CV_8U,cv::Scalar(1));cv::dilate( image, result, element );
高级形态学变换
基于膨胀和腐蚀两种基本的形态学变换,可以组合成诸如开操作、闭操作、形态学梯度、顶帽变换、黑(底)帽变换等高级的形态学变换。OpenCV 提供 cv::morphologyEx() 操作,以进行更高级的形态学变换:
void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1),
int iterations=1, int borderType=BORDER_CONSTANT, const Scalar&
borderValue=morphologyDefaultBorderValue() )
其中 op 可以是以下几种操作类型:
MORPH_OPEN - 开操作MORPH_CLOSE - 闭操作MORPH_GRADIENT - 形态学梯度MORPH_TOPHAT - “顶帽”MORPH_BLACKHAT - “黑帽”
开操作示例:
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));cv::Mcv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);
计算直方图
使用 cv::calHist 来计算直方图,得到的直方图将存放到一个 cv::MatND 类型的容器中。
1234567891011
void calcHist(const Mat* images,
int nimages,
const int* channels,
InputArray mask,
OutputArray hist,
const int* histSize,
const float** ranges,
bool uniform=true,
bool accumulate=false )
用于灰度图像
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
class Histogram1D {
Histogram1D() {
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] =
channels[0] = 0;
cv::MatND getHistogram(const cv::Mat &image) {
cv::calcHist(&image,
cv::Mat(),
cv::Mat getHistogramImage(const cv::Mat &image) {
cv::MatND hist = getHistogram(image);
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
cv::Mat histImg(histSize[0], histSize[0],
CV_8U, cv::Scalar(255));
int hpt = static_cast&int&(0.9*histSize[0]);
for (int h = 0; h & histSize[0]; ++h) {
float binVal = hist.at&float&(h);
int intensity = static_cast&int&(binVal * hpt / maxVal);
cv::line(histImg, cv::Point(h, histSize[0]),
cv::Point(h, histSize[0]-intensity),
cv::Scalar::all(0));
return histI
int histSize[1];
float hranges[2];
const float* ranges[1];
int channels[1]; };
用于彩色图像
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
class ColorHistogram {
ColorHistogram() {
histSize[0] = histSize[1] = histSize[2] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] =
ranges[1] =
ranges[2] =
channels[0] = 0;
cv::MatND getHistogram(const cv::Mat &image) {
cv::calcHist(&image,
cv::Mat(),
cv::SparseMat getSparseHistogram(const cv::Mat &image) {
cv::SparseMat hist(3, histSize, CV_32F);
cv::calcHist(&image,
cv::Mat(),
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3]; };
计算得到的直方图
直方图均衡化
在 OpenCV 中可以很方便的调用 cv::equalizeHist 来进行直方图均衡:
cv::Mat equalize(const cv::Mat &image){
cv::equalizeHist(image, result);
在其内部是使用了如下的查找表变换:
lookup.at&uchar&(i)= static_cast&uchar&(255.0*p[i]);
其中 p[i] 是灰度值小于或等于 i 的像素数量。p[i] 常被称为 累积直方图(Cumulative Histogram)。
均衡化结果
均衡化后的直方图
反投影直方图
可以利用直方图来检测一幅图像中是否含有目标图像类似的内容,所使用的算法称为反投影(back projection)。在 OpenCV 中,相应的操作是
cv::calcBackProject 操作 :
void calcBackProject(const Mat* images,
int nimages,
const int* channels,
InputArray hist,
OutputArray backProject,
const float** ranges,
double scale=1,
bool uniform=true )
例如,检测上图中类似云朵的部分,可以先使用 ROI 截取该图像中有云朵的部分作为目标图像:
cv::Mat imageROI;imageROI= image(cv::Rect(360,55,40,50));
之后提取 ROI 的直方图,用到了上面编写的 Histogram1D 类:
Histogram1Dcv::MatND hist= h.getHistogram(imageROI);
对其做归一化处理,得到一个概率分布:
cv::normalize(histogram,histogram,1.0);
然后可以对整幅图像做反投影变换,将图像中每个像素点的灰度值用归一化后的直方图的相应概率值来代替。
cv::calcBackProject(&image,
histogram,
得到如下的概率图,其中颜色越黑的部分表示概率越大:
可以进一步使用阈值操作,将可能为云朵的像素突出出来:
cv::threshold(result, result, 255*threshold,255, cv::THRESH_BINARY);
可以将这个算法封装成一个类
均值模糊
OpenCV 提供 cv::blur() 函数来对图像进行低通滤波,从而达到平滑图像的作用。
void blur(InputArray src,
OutputArray dst,
Size ksize,
Point anchor=Point(-1,-1),
int borderType=BORDER_DEFAULT )
cv::blur(image, result, cv::Size(5, 5));
均值模糊的卷积核形式如下:
|-------------|| 1/9 1/9 1/9 || 1/9 1/9 1/9 || 1/9 1/9 1/9 ||-------------|
一种加权平均的模糊算法。OpenCV 提供 cv::blur() 函数来对图像进行高斯模糊。
void GaussianBlur(InputArray src,
OutputArray dst,
Size ksize,
double sigmaX, double sigmaY=0,
int borderType=BORDER_DEFAULT )
cv::GaussianBlur(image, result, cv::Size(5,5), 1.5);
高斯模糊的卷积核根据所选的 σσ
值 sigmaX 和 sigmaY 的不同而不同。值越大,则模糊效果越明显。可以通过 cv::getGaussianKernel() 函数获取与
sigmasigma
值对应的卷积核。
下采样的步骤是:
与高斯内核卷积:
116????????1464141624164624362464162416414641????????116[]
将所有偶数行和列去除。显而易见,结果图像只有原图的四分之一。
OpenCV 提供了 cv::pyrDown() 函数来完成这两步操作:
void pyrDown(InputArray src,
OutputArray dst,
const Size& dstsize=Size(),
int borderType=BORDER_DEFAULT )
cv::Mat reducedI cv::pyrDown(image,reducedImage);
下采样常被应用于缩小图像:如果要将一幅图像缩小一倍,直接隔一行或一列去掉图像的行和列是不够的——直接去掉后,解析度会降低,如果不修改图像的空间频率,就会造成空间混淆。因此,正确的做法是先进行低通滤波,去除高频分量后再进行下采样。下文将介绍的就是迭代地使用下采样技术将图像逐步缩小成一个金字塔。
上采样不是下采样的逆操作,因为在下采样过程中原图的部分信息将会丢失。
类似的,还有一种上采样操作(不是下采样的逆操作!)。步骤为:
首先,将图像在每个方向扩大为原来的两倍,新增的行和列以 0 填充 (0)(0)。使用指定的滤波器进行卷积,获得 “新增像素” 的近似值。
OpenCV 提供了 cv::pyrUp() 函数进行下采样操作。
void pyrUp(InputArray src,
OutputArray dst,
const Size& dstsize=Size(),
int borderType=BORDER_DEFAULT )
上采样常和下采样一起用来创建。
中值滤波
OpenCV 提供 cv::medianBlur() 函数进行中值滤波:
void medianBlur(InputArray src,
OutputArray dst,
int ksize)
cv::medianBlue(image, result, 5);
中值滤波并不是一个线性滤波,因此它并不能用一个核矩阵来表示。然而,它也是通过相邻像素来决定每一个像素的值的:一个像素的值,等于其相邻像素的值的中值。中值滤波的一个典型应用是滤除椒盐噪声:
中值滤波还有用一个优点:可以保留图像边缘的锐利程度。然而,它会影响图像的材质等细节特征。
高通滤波常用来提取图像中变化比较明显的地方,例如图像边缘。
Sobel 滤波
Sobel 滤波是一种方向滤波器,它只影响竖直方向或水平方向的图像频率。该方向取决于卷积核的形状。OpenCV 提供了 cv::Sobel() 函数来进行 Sobel 滤波:
void Sobel(InputArray src,
OutputArray dst,
int ddepth,
int dx, int dy,
int ksize=3,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT )
构造一个竖直方向的 Sobel 滤波器示例:
cv::Sobel(image,sobelY,CV_8U,0,1,3,0.4,128);
构造一个水平方向的 Sobel 滤波器示例:
cv::Sobel(image,sobelX,CV_8U,1,0,3,0.4,128);
注意上面两个用例都是使用 CV_8U 这种图像类型。在这种情况下,0 值对应的像素灰度值将为 128 ,负值对应的像素将用暗一些的颜色,而正值对应的像素将用亮一些的颜色。最终的效果就如一些照片处理软件的“浮雕”特效一样:
竖直 Sobel 滤波器的结果:
水平 Sobel 滤波器的结果:
两种形式的卷积如下:
|--------||-1 0
1 ||--------|
|--------||-1 -2 -1|| 0 0
1 ||--------|
由于 Sobel 滤波器的核包含正值和负值,因此更常用的图像类型是使用16位符号整型(CV_16S)。下面将用这种类型来提取图像边缘。
计算 Sobel 算子的 L1 范数:
cv::sobel(image, sobelX, CV_16S, 1, 0);cv::sobel(image, sobelY, CV_16s, 0, 1);cv::Msobel = abs(sobelX) + abs(sobelY);
使用 convertTo() 方法将得到的 L1 范数转换成一幅图像,0 值对应的像素点为白色,而更高的值对应的像素点将用更暗的颜色表示:
double sobmin,cv::minMaxLoc(sobel, &sobmin, &sobmax);cv::Mat sobelIsobel.convertTo(sobelImage, CV_8U, -255./sobmax, 255);
得到如下的结果:
对其再进一步做阈值处理,得到一幅线条清晰的二值图像:
cv::threshold(sobelImage, sobelThresholded,
threshold, 255, cv::THRESH_BINARY);
从数学上讲,sobel 滤波器计算的是图像的梯度信息,即:
?f≡grad(f)≡[gxgy]=???f?x?f?y???f≡grad(f)≡[gxgy]=[?f?x?f?y]
由于梯度是一个二维向量,因此它有范数和方向。梯度的范数可以用来表示变化的幅度,通常使用欧几里得范数(称为 L2 范数 )来求解:
|grad(f)|=(?f?x)2+(?f?y)2---------------√|grad(f)|=(?f?x)2+(?f?y)2
然而,在图像处理中,我们通常只需要计算两个方向的一阶导数的绝对值的和,即 L1 范数 ,这个值与 L2 范数非常接近,但运算量要小很多:
|grad(f)|≈∣∣∣?f?x∣∣∣+∣∣∣?f?y∣∣∣|grad(f)|≈|?f?x|+|?f?y|
梯度向量总是指向图像中最陡峭的变化方向,这意味着在图像中,梯度方向将与图像中的边缘垂直,并且从暗的部分指向亮的部分。梯度方向可以通过下面的公式得到:
∠grad(f)=αtan(-?f?y/?f?x)∠grad(f)=αtan(-?f?y/?f?x)
OpenCV 提供了 cv::cartToPolar() 函数来获取梯度方向:
cv::Sobel(image,sobelX,CV_32F,1,0);cv::Sobel(image,sobelY,CV_32F,0,1);cv::Mat norm,cv::cartToPolar(sobelX,sobelY,norm,dir);
默认情况下,得到的方向是用辐度角来表示的,通过再添加一个参数 true 可以得到几何角。
拉普拉斯变换
拉普拉斯滤波器是另一个高通线性滤波器。OpenCV 提供了 cv::Laplacian() 函数来计算图像的拉普拉斯变换。
void Laplacian(InputArray src,
OutputArray dst,
int ddepth,
int ksize=1,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT )
一个封装好的拉普拉斯变换类 LaplacianZC 如下:
123456789101112131415161718192021222324252627282930313233343536373839
class LaplacianZC {
LaplacianZC() : aperture(3) {}
void setAperture(int a) {
aperture =
cv::Mat computeLaplacian(const cv::Mat &image) {
cv::Laplacian(image, laplace, CV_32F, aperture);
img = image.clone();
cv::Mat getLaplacianImage(double scale=-1.0) {
if (scale&0){
double lapmin,
cv::minMaxLoc(laplace, &lapmin, &lapmax);
scale = 127 / std::max(-lapmin, lapmax);
cv::Mat laplaceI
laplace.convertTo(laplaceImage, CV_8U, scale, 128);
return laplaceI
使用示例:
LaplacianZClaplacian.setAperture(7);cv::Mat flap = puteLaplacian(image);laplace = laplacian.getLaplacianImage();
拉普拉斯变换同样可以用来提取边缘:
图像的经过拉普拉斯变换后,可以利用结果的 zero-crossings 提取边缘:
遍历 Laplacian 结果图像,比对当前像素点和其左邻的像素点;如果两个像素点灰度值差值大于一个阈值,且正负号不同,则当前像素点为一个 zero-crossing 点;否则,对下一个像素重复同样的测试。
1234567891011121314151617181920212223242526272829303132
cv::Mat getZeroCrossings(float threshold=1.0) {
cv::Mat_&float&::const_iterator it=
laplace.begin&float&()+laplace.step1();
cv::Mat_&float&::const_iterator itend=
laplace.end&float&();
cv::Mat_&float&::const_iterator itup=
laplace.begin&float&();
cv::Mat binary(laplace.size(),CV_8U,cv::Scalar(255));
cv::Mat_&uchar&::iterator itout=
binary.begin&uchar&()+binary.step1();
threshold *= -1.0;
for ( ; it!= ++it, ++itup, ++itout) {
if (*it * *(it-1) & threshold)
*itout= 0;
else if (*it * *itup & threshold)
*itout= 0;
拉普拉斯变换可以提取出丰富的边缘信息,但不足在于也对噪声很敏感。
拉普拉斯变换定义为 xx
两个方向的二阶导数的和:
laplace(I)=?2I?x2+?2I?y2laplace(I)=?2I?x2+?2I?y2
它最简单的形式是用如下的 3x3 卷积核逼近的矩阵:
|--------|| 0
0||--------|
OpenCV 提供了 cv::filter2D 函数来进行图像卷积。使用它前只需先构造一个卷积核。
void filter2D(InputArray src,
OutputArray dst,
int ddepth,
InputArray kernel,
Point anchor=Point(-1,-1),
double delta=0,
int borderType=BORDER_DEFAULT )
例如,用源图像减去拉普拉斯滤波结果可以增强图像细节,相应的卷积核形式为:
|--------|| 0 -1 0 ||-1 5 -1 || 0 -1 0 ||--------|
实现如下:
12345678910111213
void sharpen2D(const cv::Mat &image, cv::Mat &result) {
cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
kernel.at&float&(1,1) = 5.0;
kernel.at&float&(0,1) = -1.0;
kernel.at&float&(2,1) = -1.0;
kernel.at&float&(1,0) = -1.0;
kernel.at&float&(1,2) = -1.0;
cv::filter2D(image, result, image.depth(), kernel);}
图像金字塔
一个图像金字塔是一系列图像的集合:
所有图像来源于同一张原始图像;通过梯次向下采样获得,直到达到某个终止条件才停止采样。
有两种类型的图像金字塔常常出现在文献和应用中:
高斯金字塔(Gaussian pyramid): 基于;拉普拉斯金字塔(Laplacian pyramid): 用来从金字塔低层图像重建上层未采样图像。
高斯金字塔
高斯金字塔为一层一层的图像,层级越高,图像越小。如下图所示,每一层都按从下到上的次序编号, 层级 (i+1)(i+1)
(表示为 Gi+1Gi+1
尺寸小于层级 i(Gi)i(Gi))。
前面已经了解到,缩小图像可以使用下采样技术。而高斯金字塔就是基于
实现的:通过对输入图像 G0G0
(原始图像) 下采样多次就会得到整个金字塔。
OpenCV 提供了一个函数 cv::buildPyramid() 用来从一幅图像创建高斯金字塔:
void buildPyramid(InputArray src,
OutputArrayOfArrays dst,
int maxlevel,
int borderType=BORDER_DEFAULT )
123456789101112131415161718192021222324
cv::Mat img = cv::imread(&./lena.png&);if (!img.data) {
perror(&Open file failed!&);
return 1;}std::vector&cv::Mat& gPcv::buildPyramid(img, gPyramid, 4);std::vector&cv::Mat&::iterator it = gPyramid.begin();std::vector&cv::Mat&::iterator itend = gPyramid.end();int i = 0;std::stringstreamfor(; it & ++it){
title && &Gaussian Pyramid & &&
cv::namedWindow(title.str());
cv::imshow(title.str(), *it);
++i;}
拉普拉斯金字塔
下采样是一个丢失信息的函数。为了恢复原来(更高分辨率)的图像,我们需要获得下采样操作中丢失的信息,这些信息可以通过上采样来预测。这些数据形成了拉普拉斯金字塔(又叫做预测残差金字塔)。下面是拉普拉斯金字塔的第
层的数学定义:
Li=Gi-UP(Gi+1)??n×nLi=Gi-UP(Gi+1)??n×n
这里的 GiGi
和 Gi+1Gi+1
分别代表第 ii
层和第 i+1i+1
层的高斯金字塔图像;UP()UP()
操作将原始图像中位置为 (x, y) 的像素映射到目标图像的 (2x+1, 2y+1) 位置;符号 ??
代表卷积操作,??
是 n×nn×n
的高斯核。OpenCV 提供的函数 cv::pyrUp() 实现的功能就如 UP(Gi+1)??n×nUP(Gi+1)??n×n
所定义。因此,我们可以使用 OpenCV 直接进行拉普拉斯运算:
Li=Gi-PyrUp(Gi+1)Li=Gi-PyrUp(Gi+1)
OpenCV 没有提供直接生成拉普拉斯金字塔的函数,但自己实现一个也很容易:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
* buildLaplacianPyramid - build a laplacian pyramid from an image * * @param src - source image * @param dst
- destination vector of maxlevel+1 image * @param maxlevel - max level */void buildLaplacianPyramid(const cv::Mat &src, std::vector&cv::Mat& &dst, const int maxlevel){
if (maxlevel & 2)
std::vector&cv::Mat& gP
cv::buildPyramid(src, gPyramid, maxlevel);
std::vector&cv::Mat&::const_iterator it = gPyramid.begin();
std::vector&cv::Mat&::const_iterator itend = gPyramid.end();
cv::Mat upsample,
while (it & itend - 1) {
current = (*it++).clone();
cv::pyrUp(*it, upsample);
dst.push_back(current - upsample);
dst.push_back(*it);} * buildLaplacianPyramid - build a laplacian pyramid from a vector of images * * @param src - vector of source images * @param dst
- destination vector of vectors of maxlevel+1 image * @param maxlevel - max level */void buildLaplacianPyramid(const std::vector&cv::Mat& &src, std::vector&std::vector&cv::Mat& & &dst, const int maxlevel){
std::vector&cv::Mat&::const_iterator it = src.begin();
std::vector&cv::Mat&::const_iterator itend = src.end();
std::vector&cv::Mat& lP
buildLaplacianPyramid(*it, lPyramid, maxlevel);
for (; it & ++it) {
dst.push_back(lPyramid);
以上两个重载函数分别根据一张图片或一系列图片生成拉普拉斯金字塔。金字塔的最顶层是一张低分辨率近似。
123456789101112131415161718192021222324
cv::Mat img = cv::imread(&./lena.png&);if (!img.data) {
perror(&Open file failed!&);
return 1;}std::vector&cv::Mat& lPbuildLaplacianPyramid(img, lPyramid, 4);std::vector&cv::Mat&::iterator it = lPyramid.begin();std::vector&cv::Mat&::iterator itend = lPyramid.end();int i = 0;std::stringstreamfor(; it & ++it){
title && &Laplacian Pyramid & &&
cv::namedWindow(title.str());
cv::imshow(title.str(), *it);
++i;}
OpenCV 提供了 cv::watershed() 函数来实现分水岭操作。
void watershed(InputArray image, InputOutputArray markers)
一个封装好的 WatershedSegmenter 类如下:
12345678910111213
class WatershedSegmenter {
void setMarkers(const cv::Mat& markerImage) {
markerImage.convertTo(markers,CV_32S);
cv::Mat process(const cv::Mat &image) {
cv::watershed(image,markers);
应用该类的步骤是:
构造一个 marker 图像(可以通过对源图像进行标记和处理);调用 WatershedSegmenter::setMarkters() 函数设置 marker;调用 WatershedSegmenter::process() 函数进行分水岭处理。
OpenCV 提供了 cv::grabCut() 函数来实现 GrabCut 操作。
void grabCut(InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, In-
putOutputArray fgdModel, int iterCount, int mode=GC_EVAL )
使用 cv::grabCut() 函数非常简单,你只需要输入一张图像,标记一些像素点属于前景图或背景图。然后该算法就会根据这些标记点分割出整幅图像前景和背景。
一种标记的方法就是直接将一部分前景的区域用矩形框起来:
image= cv::imread(&../group.jpg&);cv::Rect rectangle(10,100,380,180);
之后可以调用 cv::grabCut() 函数:
12345678910
cv::M cv::Mat bgModel, fgM cv::grabCut(image,
rectangle,
bgModel, fgModel,
cv::GC_INIT_WITH_RECT
得到的结果 result 将包含下面四种常量值:
cv::GC_BGD - 所有确定属于背景的像素(实际值为 0);cv::GC_FGD - 所有确定属于前景的像素(实际值为 1);cv::GC_PR_BGD - 所有可能属于背景的像素(实际值为 2);cv::GC_PR_FGD - 所有可能属于前景的像素(实际值为 3)。
我们可以将所有可能是前景的像素提取出来:
cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);cv::Mat foreground(image.size(), CV_8UC3,
cv::Scalar(255, 255, 255));image.copyTo(foreground,
上面得到的 foreground 图像即是应用 GrabCut 算法分割出的前景图像。
由于 cv::GC_FGD 和 cv::PR_FGD 的实际值为 1 和 3,上面的 cv::compare() 操作也可以简单的写成:
result = result & 1;
Canny 算法是一个有效的轮廓提取方法。OpenCV 提供了 cv::Canny() 函数:
void Canny(InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize=3,
bool L2gradient=false )
cv::Mcv::Canny(image,
Hough 变换是经典的提取直线的方法。OpenCV 提供了两个版本的 Hough 变换:
HoughLines
基本的版本是 cv::HoughLines() 函数:
void HoughLines(InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn=0,
double stn=0 )
参数 rho 和 theta 决定了直线查找的步长。
12345678910111213141516171819202122232425262728293031323334
cv::Mcv::Canny(image, contours, 125, 350);std::vector&cv::Vec2f&cv::HoughLines(test, lines,
1, PI/180,
std::vector&cv::Vec2f&::const_iterator it= lines.begin();while (it!=lines.end()) {
float rho= (*it)[0];
float theta= (*it)[1];
if (theta & PI/4. || theta & 3.*PI/4.) {
cv::Point pt1(rho/cos(theta),0);
cv::Point pt2((rho-result.rows*sin(theta))/
cos(theta),result.rows);
cv::line( image, pt1, pt2, cv::Scalar(255), 1);
cv::Point pt1(0,rho/sin(theta));
cv::Point pt2(result.cols,
(rho-result.cols*cos(theta))/sin(theta));
cv::line(image, pt1, pt2, cv::Scalar(255), 1);
++}
HoughLinesP
另一个是 cv::HoughLinesP() 函数,提供了 Probabilistic Hough 变换操作,与前者的不同是对直线的可能性进行了估计,以防止对一些因巧合出现的像素对齐的情况的误判:
void HoughLinesP(InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength=0,
double maxLineGap=0 )
可以将它封装成一个类
12345678910
LineFfinder.setLineLengthAndGap(100,20);finder.setMinVote(80);std::vector&cv::Vec4i& lines= finder.findLines(contours);finder.drawDetectedLines(image);cv::namedWindow(&Detected Lines with HoughP&);cv::imshow(&Detected Lines with HoughP&,image);
Hough 变换也可以用来检测圆。OpenCV 提供了 cv::HoughCircles() 实现这一操作:
void HoughCircles(InputArray image,
OutputArray circles,
int method,
double dp,
double minDist,
double param1=100,
double param2=100,
int minRadius=0,
int maxRadius=0 )
其中,method 参数目前只有一个可选值 CV_HOUGH_GRADIENT。
在进行该变换前,总是建议先进行一次高斯模糊,以降低图像噪声,提高识别率。示例:
123456789101112131415161718192021
cv::GaussianBlur(image,image,cv::Size(5,5),1.5);std::vector&cv::Vec3f&cv::HoughCircles(image, circles, CV_HOUGH_GRADIENT,
25, 100); std::vector&cv::Vec3f&::const_iterator itc= circles.begin();while (itc!=circles.end()) {
cv::circle(image,
cv::Point((*itc)[0],
(*itc)[1]),
(*itc)[2],
cv::Scalar(255),
++}
OpenCV 提供了 cv::fitLine() 函数以根据一些点的集合拟合直线:
void fitLine(InputArray points,
OutputArray line,
int distType,
double param,
double reps,
double aeps)
cv::Vec4cv::fitLine(cv::Mat(points),line,
CV_DIST_L2,
0.01,0.01);
OpenCV 提供了 cv::fitEllipse() 函数以根据一些点的集合拟合椭圆:
RotatedRect fitEllipse(InputArray points)
该操作返回一个经旋转的矩形,以表示一个椭圆的大小、形状和旋转角度。示例:
cv::RotatedRect rrect= cv::fitEllipse(cv::Mat(points));cv::ellipse(image,rrect,cv::Scalar(0));
OpenCV 提供了 cv::findContours() 函数以提取一幅图像中的闭合轮廓:
void findContours(InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int method,
Point offset=Point())
示例(只提取外部轮廓,不考虑内部轮廓):
123456789101112
std::vector&std::vector&cv::Point&&cv::findContours(image,
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); cv::Mat result(image.size(),CV_8U,cv::Scalar(255));cv::drawContours(result,contours,
cv::Scalar(0),
如果要同时查找内部轮廓,可以把 cv::findContours() 的第 3 个参数改为 CV_RETR_LIST 。如果要在查找内外所有的轮廓的同时保存轮廓的层次,可以改为
CV_RETR_TREE 。CV_RETRC_COMP 也可以得到层次,但只分成外轮廓和内轮廓两层。
边界框(bounding box)
获取一个形状的 bounding box:
cv::Rect r0= cv::boundingRect(cv::Mat(contours[0]));cv::rectangle(result,r0,cv::Scalar(0),2);
最小外接圆
floatcv::Point2cv::minEnclosingCircle(cv::Mat(contours[1]),center,radius);cv::circle(result,cv::Point(center),static_cast&int&(radius),cv::Scalar(0),2);
最小外接多边形
1234567891011121314151617
std::vector&cv::Point&cv::approxPolyDP(cv::Mat(contours[2]),
true); std::vector&cv::Point&::const_iterator itp= poly.begin();while (itp!=(poly.end()-1)) {
cv::line(result,*itp,*(itp+1),cv::Scalar(0),2);
++}cv::line(result,
*(poly.begin()),
*(poly.end()-1),cv::Scalar(20),2);
std::vector&cv::Point&cv::convexHull(cv::Mat(contours[3]),hull);
矩(moments)
1234567891011
itc= contours.begin();while (itc!=contours.end()) {
cv::Moments mom= cv::moments(cv::Mat(*itc++));
cv::circle(result,
cv::Point(mom.m10/mom.m00,mom.m01/mom.m00),
2,cv::Scalar(0),2); }
上面几步的结果:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:21415次
排名:千里之外
转载:26篇
(1)(1)(1)(1)(10)(7)(4)(1)}

我要回帖

更多关于 opencv 图像赋值 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信