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

版本说明: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;
}

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

目标分割和检测笔记(OpenCV实例精解)_第1张图片
下面是用除法的

目标分割和检测笔记(OpenCV实例精解)_第2张图片
可以发现两种方式都能有效的把背景光去掉。注意,我可是开的手电筒哦。。

3. 阀值操作

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

一幅图像包括目标物体、背景还有噪声,要想从多值的数字图像中直接提取出目标物体,最常用的方法就是设定一个全局的阈值T,用T将图像的数据分成两部分:大于T的像素群和小于T的像素群。将大于T的像素群的像素值设定为白色(或者黑色),小于T的像素群的像素值设定为黑色(或者白色)。
这里可以使用两个不同阀值的threshold函数:从图片中可以看出非兴趣区域是黑色或者很低值,删除光/背景时,可以使用一个低值.
下面是阀值化后的效果
目标分割和检测笔记(OpenCV实例精解)_第3张图片
可以看出大部分感兴趣区域已经亮起来啦,这里的阀值需要自己根据实际慢慢调整。。当然我这个兴趣区域太大啦而且是在晚上室内灯下做的,可能效果不好。
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_objects=connectedComponents(img,labels);
    if(num_objects<2//1的时候应该是只有背景(个人理解)
    {
        cout<<"No object detected"<return;
    }
    else
    {
        cout<<"Number of objects detected:"<1<//创建彩色目标输出图像
Mat output=Mat::zeros(img.rows,img.cols,CV_8UC3);//3通道8位uchar 0矩阵
RNG rng(0xFFFFFFFF);//RNG为随机数生成器类 
for(int i=1;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"<return;
    }
    else
    {
        cout<<"Number of objects detected:"<1<0xFFFFFFFF);//RNG为随机数生成器类
    for(int i=1;icout<<"Object"<"with pos:"<(i)<<"with area"<int>(i,CC_STAT_AREA)<//A
        Mat mask=(labels==i);
        output.setTo(randomColor(rng),mask);
        //使用区域绘制文本
        stringstream ss;//添加统计区域信息
        ss<<"area:"<int>(i,CC_STAT_AREA);
        putText(output,ss.str(),centroids.at(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 > 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"<return -1;
    }
    else
    {
        cout<<"objects number:"<0xFFFFFFFF);//随机数
    for(int i=0;i//依次将轮廓绘制到output上
    }
    imshow("Output",output);
}

你可能感兴趣的:(opencv基础,opencv处理图片)