您的位置:首页 > 运维架构

目标分割和检测笔记(OpenCV实例精解)

2017-10-04 20:03 645 查看
版本说明:Opencv 3.2.0版本

一.预处理

1.去噪声

根据噪声的种类选择合适的滤波器进行去除。

2.去除光亮

需从场景中的其他图像提取位于完全相同位置,没有任何对象,并且具有相同光照条件的图像。然后用一种简单的数学运算,删除光这个模式:

1)差分

2)除法

图像差分是最简单的方法。如果有光纹矩阵L和图像矩阵I,去除R的结果是他们之间的差值:

R=L-I

除法去除R的结果是

R=255×(1-I/L)

下面给出差分代码 img为需要删除光的图像,pattern位光纹遮挡

Mat removeLight(Mat img,Mat pattern)
{
Mat result;
result=pattern-img;//R=L-I
return result;
}


下面是除法

Mat removeLight(Mat img,Mat pattern)
{
Mat result,img32,pattern32;
img.covertTo(img32,CV_32F);
pattern.convertTo(pattern32,CV_32F);
//图像除以模式
result=1-(img32/pattern32);
result=result*255;//缩放以转换为8位模式
result.convertTo(result,CV_8U);//转换为8位模式
return result;
}


注意 :除法需要用32位浮点型才能分离图像。

可以用blur原图来估计背景。估计背景的方法如下:

在输入图像上使用大尺寸核矩阵模糊化,这是一种在OCR中常用的技术。虽然与上述方法存在差异,但足以消除背景类。函数如下:

Mat calculateLightPattern(Mat img)
{
Mat pattern;
blur(img,pattern,Size(img.cols/3,img.rows/3));
return pattern;
}


下面给出自己根据上述函数做的效果图。这是用差分法后的去光效果:



下面是用除法的



可以发现两种方式都能有效的把背景光去掉。注意,我可是开的手电筒哦。。

3. 阀值操作

删除背景后,还需要能在为来进行图像分割的二值图像。

threshold 全局二值化

一幅图像包括目标物体、背景还有噪声,要想从多值的数字图像中直接提取出目标物体,最常用的方法就是设定一个全局的阈值T,用T将图像的数据分成两部分:大于T的像素群和小于T的像素群。将大于T的像素群的像素值设定为白色(或者黑色),小于T的像素群的像素值设定为黑色(或者白色)。

这里可以使用两个不同阀值的threshold函数:从图片中可以看出非兴趣区域是黑色或者很低值,删除光/背景时,可以使用一个低值.

下面是阀值化后的效果



可以看出大部分感兴趣区域已经亮起来啦,这里的阀值需要自己根据实际慢慢调整。。当然我这个兴趣区域太大啦而且是在晚上室内灯下做的,可能效果不好。

double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type )

参数说明:

src:输入图像。

dst:输出图像。

thresh:阀值T。

maxval:向上最大值

type:类型

详细解释大家可以看看这位大佬总结的http://blog.csdn.net/guduruyu/article/details/68059450

二.分割

这里介绍了用于分割阀值图像的两种技术:

1)连通区域

2)findContours函数

1.连通区域算法

连通区域是分割识别二进制图像部分的常用算法。

连通域是使用8或者4链接像素标记图像的迭代算法。

4连通:只有横向和竖向连通。

8连通:在4连通的基础上加上了对角连通。

OpenCV3中提供有下面两个不同功能的连通区域算法:

1)connectedComponents(image,labels,connectivity=8,type=CV_32S)

2)connectedComponentsWithStats(image,labels,stats,centroids,connectivity=8,ltype=CV_32S)

这两个函数返回一个表示检测到几个标签的整数,标签0表示背景,1表示第一个对象,然后依次类推。

connectedComponents的参数说明:

Image :待标记的输入图像

Label :这是一个Mat,是输出,其输出一个与输入大小相同的图像,每个像素都有各自标签值,0代表背景,1代表连通区域的第一个对象,2代表第二个对象,以此类推。

Connectivity:有两个可能值,4或者8。分别表示4连通方式还是8连通方式。

Type:这是想要使用的标签图像类型:只允许两种类型,CV_32S或CV_16U.

connectedComponentsWithStats的参数说明:

Image :待标记的输入图像

Label :这是一个Mat,是输出,其输出一个与输入大小相同的图像,每个像素都有各自标签值,0代表背景,1代表连通区域的第一个对象,2代表第二个对象,以此类推。

Connectivity:有两个可能值,4或者8。分别表示4连通方式还是8连通方式。

Type:这是想要使用的标签图像类型:只允许两种类型,CV_32S或CV_16U.

Stats :包括背景标签在内的所有标签的输出参数。以下统计值可以通过统计数据(标签。列)访问,列也同样定义,具体如下:

××××××CC_STAT_LEFT:这是连通区域对象的左边x坐标

××××××CC_STAT_TOP:这是连通区域对象的顶层y坐标

××××××CC_STAT_WIDTH:这定义连通区域对象边框的宽度

××××××CC_STAT_HEIGHT:这定义连通区域对象边框的高度

××××××CC_STAT_AREA:这是连通区域对象的像素(区)数量

Centroids:每个标签(包含背景)的浮点型质心点。

下面给出函数,就不给图了(总不能把我自己分割类吧),书上是针对一些零件分割的,自行想象上面已出的图,效果应该不错。

void ConnectedComponents(Mat img)
{
Mat lebels;
//检测到连通区域的数目给lebels
int num_ob
4000
jects=connectedComponents(img,labels);
if(num_objects<2)//1的时候应该是只有背景(个人理解)
{
cout<<"No object detected"<<endl;
return;
}
else
{
cout<<"Number of objects detected:"<<num_objects-1<<endl;
}
//创建彩色目标输出图像
Mat output=Mat::zeros(img.rows,img.cols,CV_8UC3);//3通道8位uchar 0矩阵
RNG rng(0xFFFFFFFF);//RNG为随机数生成器类
for(int i=1;i<num_objects;i++)
{
Mat mask=(labels==i);//A
output.setTo(randomColor(rng),mask);//B
}
imshow("result",output);
}


A:此处括号是我自己加的 便于理解,个人认为labels是mat类,里面存以不同标签号的图

B:randomColor其实就是将随机数/255的作用。 setTo(x,mask)自己理解为只处理mask,即矩阵不为0的地方,并将x得到的三通到颜色给予mask

下面用connectedComponentsWithStats()来显示更多信息的输出结果图像。

下面是其函数:

void ConnectedComponentsStats(Mat img)
{
//连通区域统计信息的三个量
Mat labels,stats,centroids;
int num_objects=connectedComponentsWithStats(img,labels,stats,centroids);
//检查检测到连通域的数目
if(num_objects<2)//1的时候应该是只有背景(个人理解)
{
cout<<"No object detected"<<endl;
return;
}
else
{
cout<<"Number of objects detected:"<<num_objects-1<<endl;
}
Mat output=Mat::zeros(img.rows,img.cols,CV_8UC3);
RNG rng(0xFFFFFFFF);//RNG为随机数生成器类
for(int i=1;i<num_objects;i++)
{
cout<<"Object"<<i<<"with pos:"<<centroids.at<Point2d>(i)<<"with area"<<stats.at<int>(i,CC_STAT_AREA)<<endl;//A
Mat mask=(labels==i);
output.setTo(randomColor(rng),mask);
//使用区域绘制文本
stringstream ss;//添加统计区域信息
ss<<"area:"<<stats.at<int>(i,CC_STAT_AREA);
putText(output,ss.str(),centroids.at<Point2d>(i),FONT_HERSHEY_SIMPLEX,0.4,Scalar(255,255,255));
//在output上描述信息(这里利用了putText函数)
}
imshow("result",output);
}


A: centroids.at < Point2d > (i)表示第i个连通域的质心 stats.at < int>(i,CC_STAT_AREA)为连通域像素的数量。

2.查找轮廓(findContours算法)

其函数声明如下:

void findContours(InputOutputArray image, OutputArrayofArrays contours, OutputArray hierarchy, int mode,int method,Point offset=Point())

每个参数的含义如下:

image二进制输入图像。

contours:输出轮廓,每个检测出来的输出轮廓是点向量,通常用vector < vector< Point> >类型来保存。

hierarchy:储存轮廓层次结构的可选输出向量,可以得到每个轮廓之间的关系的图像拓扑。具体可以在learning Opencv 里面看。

mode:检测轮廓的方法:

××××××RETR_EXTERNAL:检测外部轮廓

××××××RETR_LIST:检索没有建立层次结构的轮廓

××××××RETR_CCOMP:检索有两个级别的层次结构的所有轮廓:外部和孔,如果另一个对象在一个洞里,那么将其放在层次结构的顶层。

method:检测轮廓形状的近似方法:

××××××CV_CHAIN_APPROX_NONE:这并不适用于近似任何轮廓和存储所有的轮廓点。

××××××CV_CHAIN_APPROX_SIMPLE:这压缩存储水平,垂直和对角线段的起始点和结束点。例如,矩形就只会有4个角点。

××××××CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:这适用于Teh-Chin chain近似算法。

offset:用于转移所有轮廓的可选点。当ROI工作中,这是非常有用的,而且需要检索全局位置。

这里再给出与之成对使用的函数DrawContours:

void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )

image:用来绘制轮廓的输出图像

contours:轮廓向量一般与findContours里面一致

contourIdx:指示轮廓绘制的数字。即绘制contours里面的第几个轮廓,若为负数,则全部绘出

Scalar& color:绘制轮廓的颜色

thickness:轮廓线的粗细。若为负数则填充整个轮廓内部。

lineType:线型

hierarchy:绘制轮廓层次结构的可选输出向量,可以得到每个轮廓之间的关系的图像拓扑。具体可以在learning Opencv 里面看。

maxLevel:可选参数,当层次结构参数可用时,可对他进行设置。若设置为0,只绘制指定轮廓;若设置为1,这个函数绘制当前轮廓及嵌套;若为2,算法绘制所有指定轮廓层次。

offset:可选参数,改变轮廓。

下面给出代码及个人理解:

void FindContoursBasic(Mat img)
{
vector<vector<Point> > contours;//点集数组.那个空格必须有,不然会被读成>>。
findContours(img,contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
Mat output=Mat::zeros(img.rows,img.cols,CV_8UC3);//定义绘制板
if(contours.size()==0)
{
cout<<"No objects founded"<<endl;
return -1;
}
else
{
cout<<"objects number:"<<contours.size()<<endl;
}
RNG rng(0xFFFFFFFF);//随机数
for(int i=0;i<contours.size(),i++)
{
drawContours(output,contours,i,randomColor(rng));//依次将轮廓绘制到output上
}
imshow("Output",output);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  opencv 实例