opencv opencv3 findcontourss中数组存放逆时针吗

当前位置:
opencv如何根据已经得到的轮廓(contours)从原图像中提取图像
opencv如何根据已经得到的轮廓(contours)从原图像中提取图像
来源:网络整理&&&&&时间: 5:41:41&&&&&关键词:
关于网友提出的“ opencv如何根据已经得到的轮廓(contours)从原图像中提取图像”问题疑问,本网通过在网上对“ opencv如何根据已经得到的轮廓(contours)从原图像中提取图像”有关的相关答案进行了整理,供用户进行参考,详细问题解答如下:
问题: opencv如何根据已经得到的轮廓(contours)从原图像中提取图像
描述:opencv轮廓图像提取c++
已经得到轮廓数据contours,如何跟据轮廓数据从原图中抠出相应部分区域并重新保存
我的图像是Mat格式,不能用cvimageroi来提取
请问有没有相应的提取函数
感觉很简单的一个问题就是解决不了,心好累解决方案1:
楼主解决了么,怎么做的啊,求教~
解决方案2:
楼主这个问题解决了么,求助。。。解决方案3:
《计算机图形图像处理》
解决方案4:
Mat是C++类,里面有成员函数
解决方案5:
怎么不能?IplImage和Mat是可以互相转换的
以上介绍了“ opencv如何根据已经得到的轮廓(contours)从原图像中提取图像”的问题解答,希望对有需要的网友有所帮助。
本文网址链接:http://www.codes51.com/itwd/4025586.html
上一篇: 下一篇:opencv contours的问题
时间: 19:17:55
&&&& 阅读:278
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&第一个问题:
问题:假如我有如下一张图,我要把边上两个小点去除,又要保留大轮廓内部的空洞,怎么办?
函数原型:
C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())这两个重载函数只有一个hierarchy的差别。说明如下:
测试:& &用以下代码测试一下 contours 和 hierarchy 是怎么对轮廓进行标号的?选择mode = CV_RETR_CCOMP 双层结构。
#include &opencv2\opencv.hpp&
#include &time.h&
//测试findContours,drawContours函数用法
bool verify(vector&Point& input, int min, int max);
int main()
Mat src = imread(&test.bmp&, 1);
Mat dst = Mat::zeros(src.size(), src.type());
//cout&&src.channels();
//画图软件生成3通道图
cvtColor(src, gray, CV_BGR2GRAY);
threshold(gray, thre, 127, 255, CV_THRESH_BINARY );
//thre = gray & 1;
//这种写法可以替代上面那一句
imshow(&thre&, thre);
imwrite(&thre.bmp&, thre);
vector& vector&Point& &
vector&Vec4i&
findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
//计算每个轮廓的面积
float temp = 0;
for(int i=0; i&contours.size(); ++i) {
temp = fabs(contourArea(contours[i]));
cout&&&i=&&&i&&&
area=&&&temp&&
//我要把单独的小轮廓去掉,而保留大轮廓内部的孔洞
int idx = 0;
for( ; idx &=0; idx = hierarchy[idx][0])
if(verify(contours[idx], 200, 90000)) {
Scalar color( rand()&255, rand()&255, rand()&255);
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy);
imshow(&dst&,dst);
imwrite(&dst.bmp&, dst);
while( 1 )
int key = waitKey(0);
//获取键盘按键
if( (key & 255) == 27 )
//判断ESC是否按下,若按下便退出
cout && &程序退出...........\n&;
bool verify(vector&Point& input, int min, int max) {
float area = fabs(contourArea(input));
if((area&min) & (area&max))
我计算了每个轮廓的面积, 判断面积在指定范围内就把该轮廓画出来。在查找轮廓的时候,我只查找&hierarchy[i][0],就是不去处理子轮廓。&发现已经达到要求了。
contours 有6个,hierarchy 也有6个,对应测试图中总共6个内外轮廓。
hierarchy[i] 表示第 i 个轮廓,有四个值&hierarchy[i][0],&hierarchy[i][1],&hierarchy[i][2],&hierarchy[i][3], 分别对应next, previous, first child, paraent. &它们的值是轮廓编号,如果没有响应的轮廓则等于-1. 从局部变量里面查看,这些值是这样子的:
计算每个轮廓的面积:
因此推测出轮廓序号是这样子的:
这里面关键的一点是:填充序号为0和2的轮廓的时候,CV_FILLED, 并没有把内轮廓也填充掉,而是填充了环形区域!
如果我把 mode 改成 CV_RETR_LIST, 单层轮廓。
代码和测试结果图:
findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
for(int i=0; i&contours.size(); ++i) {
if(verify(contours[i], 200, 90000)) {
Scalar color( rand()&255, rand()&255, rand()&255);
drawContours(dst, contours, i, color, CV_FILLED, 8, hierarchy);
果真就把内部轮廓也一起填充了。
轮廓重新排列,根据面积和 hierarchy 的值确定轮廓是这样排序的:
先排内部轮廓,再排外部轮廓(可能是随机的)。
第二个问题:contours.erase() 函数怎么用
把上面的代码该成:
int idx = 0;
for( ; idx &=0; idx = hierarchy[idx][0])
if(verify(contours[idx], 0, 200)) {
contours.erase(contours.begin() + idx);
}这样用会报错,当idx = 5 时, 已经不存在 contours[5]; 因为执行 contours.erase() 之后,该 contour 会被清除,后面的 contour 会补位,contours.size()的值变小了。也就是,contours &和 hierarchy 的序号不同步了。
只用contours 序号不会错, 但是我又想要实现最开始给出的那个功能,就要用到hierarchy的信息。怎么办呢?
暂时没有找出办法。
第三个问题:轮廓面积计算结果和像素点总数的关系
findContours() &提取轮廓, contourArea() 计算面积,得到的结果跟我数像素点个数是不一样的。
测试代码和结果:
findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
//计算每个轮廓的面积
float temp = 0;
for(int i=0; i&contours.size(); ++i) {
temp = fabs(contourArea(contours[i]));
cout&&&i=&&&i&&&
Contour_Area=&&&temp&&
//直接计算面积
float temp1 = countNonZero(thre);
cout&&&NonZero=&&&temp1&&
只有一个轮廓,通过提取轮廓再求轮廓内部面积得到100, 直接数连通域像素点个数得到121。差别还是很大的。多次试验发现,该算法是取连通域边界像素中心点,连接起来,成为一个轮廓。测试图中的正方形边长为11个像素点,Contour_Area = 10*10=100; & NonZero = 11*11 = 121. 示意图如下:
如果提取轮廓再算面积是 3*3=9; 而连通域像素点个数是4*4=16;
因此,countArea() 函数算出来的轮廓面积常常出现 “0“值就可以理解了,比如有的轮廓厚度只有两像素,都是边缘线,那计算出来的厚度就等于0;
第四个问题:findContours() & &method 参数的影响
method =&CV_CHAIN_APPROX_NONE &存储所有的轮廓点,也就是任意两个相邻的轮廓点8领域相连。存储的轮廓点都是紧挨着的,不会跳过。
method =&CV_CHAIN_APPROX_SIMPLE & 压缩水平、垂直或斜对角方向的片段,连接两个端点,只存储两个端点像素点。比如矩形,只存储四个点。
测试图片:
& & & & &&&& &&
两种method的计算结果竟然相同。只是contours[i] &里面存储的点数不一样。边界点存储的少了难道不会丢失信息吗?drawContours 画出来的边界是否完全一样?
放大了看边界也是一样的,计算了两者面积也是一样的。
既然这样,CV_CHAIN_APPROX_SIMPLE 还可以减少存储,CV_CHAIN_APPROX_NONE 用在哪里呢?
第五个问题:findContours() 提取轮廓 & &drawContours() 再画出轮廓,面积和原来的会不会变化?
仍用图4.1做测试,结果:
上面三行 contourArea() 算出的三个连通域的面积,sun_area 是三个值的和;(和第一个问题结果一样,该面积小于连通域像素点个数)。
src_area 是原图像连通域像素点个数; dst_area 是经过提取轮廓再画出轮廓后计算出的连通域面积,两者是一样的。
说明连着用这两个函数不会改变原来的形状,相当于复制了一遍。
第六个问题:计算外轮廓面积是环形面积吗?会把内轮廓一块算上吗?
图中的两个大圆是一样的。&
findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
虽然排序不同,但是不同参数下的面积是一样的,说明计算外轮廓包围面积的时候是实心的,不会把内轮廓抠掉。
如果我想得到环形的面积,要用外轮廓-内轮廓。
好了,实验到这里。下一步可以去看 contours.cpp 源码。手册上面说了,主要算法叫“ Suzuki85” &(铃木算法?)。
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:http://blog.csdn.net/yiqiudream/article/details/
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!OpenCV之findContours函数参数说明及相关函数
findContours函数,这个函数的原型为:
void findContours(InputOutputArray image, OutputArrayOfArrays
contours, OutputArray hierar-
chy, int mode, int method, Point offset=Point())
输入图像image必须为一个2值单通道图像
contours参数为检测的轮廓数组,每一个轮廓用一个point类型的vector表示
hiararchy参数和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][
0 ] ~hierarchy[ i ][ 3
],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为负数。
mode表示轮廓的检索模式
CV_RETR_EXTERNAL表示只检测外轮廓
CV_RETR_LIST检测的轮廓不建立等级关系
CV_RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
CV_RETR_TREE建立一个等级树结构的轮廓。具体参考contours.c这个demo
method为轮廓的近似办法
CV_CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
CV_CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain
offset表示代表轮廓点的偏移量,可以设置为任意值。对ROI图像中找出的轮廓,并要在整个图像中进行分析时,这个参数还是很有用的。
findContours后会对输入的2值图像改变,所以如果不想改变该2值图像,需创建新mat来存放,findContours后的轮廓信息contours可能过于复杂不平滑,可以用approxPolyDP函数对该多边形曲线做适当近似
contourArea函数可以得到当前轮廓包含区域的大小,方便轮廓的筛选
findContours经常与drawContours配合使用,用来将轮廓绘制出来。其中第一个参数image表示目标图像,第二个参数contours表示输入的轮廓组,每一组轮廓由点vector构成,第三个参数contourIdx指明画第几个轮廓,如果该参数为负值,则画全部轮廓,第四个参数color为轮廓的颜色,第五个参数thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部,第六个参数lineType为线型,第七个参数为轮廓结构信息,第八个参数为maxLevel
得到了复杂轮廓往往不适合特征的检测,这里再介绍一个点集凸包络的提取函数convexHull,输入参数就可以是contours组中的一个轮廓,返回外凸包络的点集
还可以得到轮廓的外包络矩形,使用函数boundingRect,如果想得到旋转的外包络矩形,使用函数minAreaRect,返回值为RotatedRect;也可以得到轮廓的外包络圆,对应的函数为minEnclosingCircle;想得到轮廓的外包络椭圆,对应的函数为fitEllipse,返回值也是RotatedRect,可以用ellipse函数画出对应的椭圆
如果想根据多边形的轮廓信息得到多边形的多阶矩,可以使用类moments,这个类可以得到多边形和光栅形状的3阶以内的所有矩,类内有变量m00,m10,m01,m20,m11,m02,m30,m21,m12,m03,比如多边形的质心为
x = m10 / m00,y = m01 / m00。
如果想获得一点与多边形封闭轮廓的信息,可以调用pointPolygonTest函数,这个函数返回值为该点距离轮廓最近边界的距离,为正值为在轮廓内部,负值为在轮廓外部,0表示在边界上。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。学习笔记:使用 OpenCV 识别 QRCode
招聘信息:
背景识别二维码的项目数不胜数,每次都是开箱即用,方便得很。这次想用 OpenCV 从零识别二维码,主要是温习一下图像处理方面的基础概念,熟悉 OpenCV 的常见操作,以及了解二维码识别和编码的基本原理。作者本人在图像处理方面还是一名新手,采用的方法大多原始粗暴,如果有更好的解决方案欢迎指教。QRCode二维码有很多种,这里我选择的是比较常见的 QRCode 作为探索对象。QRCode 全名是 Quick Response Code,是一种可以快速识别的二维码。尺寸QRCode 有不同的 Version ,不同的 Version 对应着不同的尺寸。将最小单位的黑白块称为 module ,则 QRCode 尺寸的公式如下:Version V = ((V-1)*4 + 21) ^ 2 modules常见的 QRCode 一共有40种尺寸:Version 1 : 21 * 21 modulesVersion 2 : 25 * 25 modules…Version 40: 177 * 177 modules分类QRCode 分为 Model 1、Model 2、Micro QR 三类:Model 1 :是 Model 2 和 Micro QR 的原型,有 Version 1 到 Version 14 共14种尺寸。Model 2 :是 Model 1 的改良版本,添加了对齐标记,有 Version 1 到 Version 40 共40种尺寸。Micro QR :只有一个定位标记,最小尺寸是 11*11 modules 。组成QRCode 主要由以下部分组成:1 - Position Detection Pattern:位于三个角落,可以快速检测二维码位置。2 - Separators:一个单位宽的分割线,提高二维码位置检测的效率。3 - Timing Pattern:黑白相间,用于修正坐标系。4 - Alignment Patterns:提高二维码在失真情况下的识别率。5 - Format Information:格式信息,包含了错误修正级别和掩码图案。6 - Data:真正的数据部分。7 - Error Correction:用于错误修正,和 Data 部分格式相同。具体的生成原理和识别细节可以阅读文末的参考文献,比如耗子叔的这篇《》。由于二维码的解码步骤比较复杂,而本次学习重点是数字图像处理相关的内容,所以本文主要是解决二维码的识别定位问题,数据解码的工作交给第三方库(比如 )完成。OpenCV在开始识别二维码之前,还需要补补课,了解一些图像处理相关的基本概念。contours轮廓(contour)可以简单理解为一段连续的像素点。比如一个长方形的边,比如一条线,比如一个点,都属于轮廓。而轮廓之间有一定的层级关系,以下图为例:主要说明以下概念:external & internal:对于最大的包围盒而言,2 是外部轮廓(external),2a 是内部轮廓(internal)。parent & child:2 是 2a 的父轮廓(parent),2a 是 2 的子轮廓(child),3 是 2a 的子轮廓,同理,3a 是 3 的子轮廓,4 和 5 都是 3a 的子轮廓。external | outermost:0、1、2 都属于最外围轮廓(outermost)。hierarchy level:0、1、2 是同一层级(same hierarchy),都属于 hierarchy-0 ,它们的第一层子轮廓属于 hierarchy-1 。first child:4 是 3a 的第一个子轮廓(first child)。实际上 5 也可以,这个看个人喜好了。在 OpenCV 中,通过一个数组表达轮廓的层级关系:[Next, Previous, First_Child, Parent]Next:同一层级的下一个轮廓。在上图中, 0 的 Next 就是 1 ,1 的 Next 就是 2 ,2 的 Next 是 -1 ,表示没有下一个同级轮廓。Previous:同一层级的上一个轮廓。比如 5 的 Previous 是 4, 1 的 Previous 就是 0 ,0 的 Previous 是 -1 。First_Child:第一个子轮廓,比如 2 的 First_Child 就是 2a ,像 3a 这种有两个 Child ,只取第一个,比如选择 4 作为 First_Child 。Parent:父轮廓,比如 4 和 5 的 Parent 都是 3a ,3a 的 Parent 是 3 。关于轮廓层级的问题,参考阅读:《》findContours了解了 contour 相关的基础概念之后,接下来就是在 OpenCV 里的具体代码了。findContours 是寻找轮廓的函数,函数定义如下:cv2.findContours(image, mode, method) → image, contours, hierarchy其中:image:资源图片,8 bit 单通道,一般需要将普通的 BGR 图片通过 cvtColor 函数转换。mode:边缘检测的模式,包括:CV_RETR_EXTERNAL:只检索最大的外部轮廓(extreme outer),没有层级关系,只取根节点的轮廓。CV_RETR_LIST:检索所有轮廓,但是没有 Parent 和 Child 的层级关系,所有轮廓都是同级的。CV_RETR_CCOMP:检索所有轮廓,并且按照二级结构组织:外轮廓和内轮廓。以前面的大图为例,0、1、2、3、4、5 都属于第0层,2a 和 3a 都属于第1层。CV_RETR_TREE:检索所有轮廓,并且按照嵌套关系组织层级。以前面的大图为例,0、1、2 属于第0层,2a 属于第1层,3 属于第2层,3a 属于第3层,4、5 属于第4层。method:边缘近似的方法,包括:CV_CHAIN_APPROX_NONE:严格存储所有边缘点,即:序列中任意两个点的距离均为1。CV_CHAIN_APPROX_SIMPLE:压缩边缘,通过顶点绘制轮廓。drawContoursdrawContours 是绘制边缘的函数,可以传入 findContours 函数返回的轮廓结果,在目标图像上绘制轮廓。函数定义如下:Python: cv2.drawContours(image, contours, contourIdx, color) → image其中:image:目标图像,直接修改目标的像素点,实现绘制。contours:需要绘制的边缘数组。contourIdx:需要绘制的边缘索引,如果全部绘制则为 -1。color:绘制的颜色,为 BGR 格式的 Scalar 。thickness:可选,绘制的密度,即描绘轮廓时所用的画笔粗细。lineType: 可选,连线类型,分为以下几种:LINE_4:4-connected line,只有相邻的点可以连接成线,一个点有四个相邻的坑位。LINE_8:8-connected line,相邻的点或者斜对角相邻的点可以连接成线,一个点有四个相邻的坑位和四个斜对角相邻的坑位,所以一共有8个坑位。LINE_AA:antialiased line,抗锯齿连线。hierarchy:可选,如果需要绘制某些层级的轮廓时作为层级关系传入。maxLevel:可选,需要绘制的层级中的最大级别。如果为1,则只绘制最外层轮廓,如果为2,绘制最外层和第二层轮廓,以此类推。moments矩(moment)起源于物理学的力矩,最早由阿基米德提出,后来发展到统计学,再后来到数学进行归纳。本质上来讲,物理学和统计学的矩都是数学上矩的特例。物理学中的矩表示作用力促使物体绕着支点旋转的趋向,通俗理解就像是拧螺丝时用的扭转的力,由矢量和作用力组成。数学中的矩用来描述数据分布特征的一类数字特征,例如:算术平均数、方差、标准差、平均差,这些值都是矩。在实数域上的实函数 f(x) 相对于值 c 的 n 阶矩为:常用的矩有两类:原点矩(raw moment):相对原点的矩,即当 c 为 0 的时候。1阶原点矩为期望,也成为中心。中心矩(central moment):相对于中心点的矩,即当 c 为 E(x) 的时候。1阶中心矩为0,2阶中心矩为方差。到了图像处理领域,对于灰度图(单通道,每个像素点由一个数值来表示)而言,把坐标看成二维变量 (X, Y),那么图像可以用二维灰度密度函数 I(x, y) 来表示。简单来讲,图像的矩就是图像的像素相对于某个点的分布情况统计,是图像的一种特征描述。raw moment图像的原点矩(raw moment)是相对于原点的矩,公式为:对于图像的原点矩而言:M00 相当于权重系数为 1 。将所有 I(x, y) 相加,对于二值图像而言,相当于将每个点记为 1 然后求和,也就是图像的面积;对于灰度图像而言,则是图像的灰度值的和。M10 相当于权重为 x 。对二值图像而言,相当于将所有的 x 坐标相加。M01 相当于权重为 y 。对二值图像而言,相当于将所有的 y 坐标相加。图像的几何中心(centroid)等于 (M10 / M00 , M01 / M00)。central moment图像的中心矩(central moment)是相对于几何中心的矩,公式为:可以看到,中心矩表现的是图像相对于几何中心的分布情况。一个通用的描述中心矩和原点矩关系的公式是:中心矩在图像处理中的一个应用便是寻找不变矩(invariant moments),这是一个高度浓缩的图像特征。所谓的不变性有三种,分别对应图像处理中的三种仿射变换:平移不变性(translation invariants):中心矩本身就具有平移不变性,因为它是相对于自身的中心的分布统计,相当于是采用了相对坐标系,而平移改变的是整体坐标。缩放不变性(scale invariants):为了实现缩放不变性,可以构造一个规格化的中心矩,即将中心矩除以 (1+(i+j)/2) 阶的0阶中心矩,具体公式见 《》。旋转不变性(rotation invariants):通过2阶和3阶的规格化中心矩可以构建7个不变矩组,构成的特征量具有旋转不变性。具体可以看 《》。Hu moment 和 Zernike moment 之类的内容就不继续展开了,感兴趣的可以翻阅相关文章。OpenCV + QRCode接下来就是将 QRCode 和 OpenCV 结合起来的具体使用了。初步构想的识别步骤如下:加载图像,并且进行一些预处理,比如通过高斯模糊去噪。通过 Canny 边缘检测算法,找出图像中的边缘寻找边缘中的轮廓,将嵌套层数大于 4 的边缘找出,得到 Position Detection Pattern 。如果上一步得到的结果不为 3 ,则通过 Timing Pattern 去除错误答案。计算定位标记的最小矩形包围盒,获得三个最外围顶点,算出第四个顶点,从而确定二维码的区域。计算定位标记的几何中心,连线组成三角形,从而修正坐标,得到仿射变换前的 QRCode 。在接下来的内容里,将会尝试用 OpenCV 识别下图中的二维码:加载图像首先加载图像,并通过 matplotlib 显示图像查看效果:%matplotlib inlineimport cv2from matplotlib import pyplot as pltimport numpy as npdef show(img, code=cv2.COLOR_BGR2RGB): & &cv_rgb = cv2.cvtColor(img, code) & &fig, ax = plt.subplots(figsize=(16, 10)) & &ax.imshow(cv_rgb) & &fig.show()img = cv2.imread(&#39;1.jpg&#39;)show(img)OpenCV 中默认是 BGR 通道,通过 cvtColor 函数将原图转换成灰度图:img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)边缘检测有了灰度图之后,接下来用 Canny 边缘检测算法检测边缘。Canny 边缘检测算法主要是以下几个步骤:用高斯滤波器平滑图像去除噪声干扰(低通滤波器消除高频噪声)。生成每个点的亮度梯度图(intensity gradients),以及亮度梯度的方向。通过非极大值抑制(non-maximum suppression)缩小边缘宽度。非极大值抑制的意思是,只保留梯度方向上的极大值,删除其他非极大值,从而实现锐化的效果。通过双阈值法(double threshold)寻找潜在边缘。大于高阈值为强边缘(strong edge),保留;小于低阈值则删除;不大不小的为弱边缘(weak edge),待定。通过迟滞现象(Hysteresis)处理待定边缘。弱边缘有可能是边缘,也可能是噪音,判断标准是:如果一个弱边缘点附近的八个相邻点中,存在一个强边缘,则此弱边缘为强边缘,否则排除。在 OpenCV 中可以直接使用 Canny 函数,不过在那之前要先用 GaussianBlur 函数进行高斯模糊:img_gb = cv2.GaussianBlur(img_gray, (5, 5), 0)接下来使用 Canny 函数检测边缘,选择 100 和 200 作为高低阈值:edges = cv2.Canny(img_gray, 100 , 200)执行结果如下:可以看到图像中的很多噪音都被处理掉了,只剩下了边缘部分。寻找定位标记有了边缘之后,接下来就是通过轮廓定位图像中的二维码。二维码的 Position Detection Pattern 在寻找轮廓之后,应该是有6层(因为一条边缘会被识别出两个轮廓,外轮廓和内轮廓):所以,如果简单处理的话,只要遍历图像的层级关系,然后嵌套层数大于等于5的取出来就可以了:img_fc, contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)hierarchy = hierarchy[0]found = []for i in range(len(contours)): & &k = i & &c = 0 & &while hierarchy[k][2] != -1: & & & &k = hierarchy[k][2] & & & &c = c + 1 & &if c >= 5: & & & &found.append(i)for i in found: & &img_dc = img.copy() & &cv2.drawContours(img_dc, contours, i, (0, 255, 0), 3) & &show(img_dc)绘制结果如下:定位筛选接下来就是把所有找到的定位标记进行筛选。如果刚好找到三个那就可以直接跳过这一步了。然而,因为这张图比较特殊,找出了四个定位标记,所以需要排除一个错误答案。讲真,如果只靠三个 Position Detection Pattern 组成的直角三角形,是没办法从这四个当中排除错误答案的。因为,一方面会有形变的影响,比如斜躺着的二维码,本身三个顶点连线就不是直角三角形;另一方面,极端情况下,多余的那个标记如果位置比较凑巧的话,完全和正确结果一模一样,比如下面这种情况:所以我们需要 Timing Pattern 的帮助,也就是定位标记之间的黑白相间的那两条黑白相间的线。解决思路大致如下:将4个定位标记两两配对将他们的4个顶点两两连线,选出最短的那两根如果两根线都不符合 Timing Pattern 的特征,则出局寻找定位标记的顶点找的的定位标记是一个轮廓结果,由许多像素点组成。如果想找到定位标记的顶点,则需要找到定位标记的矩形包围盒。先通过 minAreaRect 函数将检查到的轮廓转换成最小矩形包围盒,并且绘制出来:draw_img = img.copy()for i in found: & &rect = cv2.minAreaRect(contours[i]) & &box = cv2.boxPoints(rect) & &box = np.int0(box) & &cv2.drawContours(draw_img,[box], 0, (0,0,255), 2)show(draw_img)绘制如下:这个矩形包围盒的四个坐标点就是顶点,将它存储在 boxes 中:boxes = []for i in found: & &rect = cv2.minAreaRect(contours[i]) & &box = cv2.boxPoints(rect) & &box = np.int0(box) & &box = map(tuple, box) & &boxes.append(box)定位标记的顶点连线接下来先遍历所有顶点连线,然后从中选择最短的两根,并将它们绘制出来:def cv_distance(P, Q): & &return int(math.sqrt(pow((P[0] - Q[0]), 2) + pow((P[1] - Q[1]),2)))def check(a, b): & & & &s1_ab = () & &s2_ab = () & & & &s1 = np.iinfo(&#39;i&#39;).max & &s2 = s1 & &for ai in a: & & & &for bi in b: & & & & & &d = cv_distance(ai, bi) & & & & & &if d < s2: & & & & & & & &if d < s1: & & & & & & & & & &s1_ab, s2_ab = (ai, bi), s1_ab & & & & & & & & & &s1, s2 = d, s1 & & & & & & & &else: & & & & & & & & & &s2_ab = (ai, bi) & & & & & & & & & &s2 = d & & & & & & & & &a1, a2 = s1_ab[0], s2_ab[0] & &b1, b2 = s1_ab[1], s2_ab[1] & & & &cv2.line(draw_img, a1, b1, (0,0,255), 3) & &cv2.line(draw_img, a2, b2, (0,0,255), 3)for i in range(len(boxes)): & &for j in range(i+1, len(boxes)): & & & &check(boxes[i], boxes[j])show(draw_img)绘制结果如下:获取连线上的像素值有了端点连线,接下来需要获取连线上的像素值,以便后面判断是否是 Timing Pattern 。在这之前,为了更方便的判断黑白相间的情况,先对图像进行二值化:th, bi_img = cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY)接下来是获取连线像素值。由于 OpenCV3 的 Python 库中没有 LineIterator ,只好自己写一个。在《》这个问答里找到了可用的直线遍历函数,可以直接使用。以一条 Timing Pattern 为例:打印其像素点看下结果:[ 255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255.]修正端点位置照理说, Timing Pattern 的连线,像素值应该是黑白均匀相间才对,为什么是上面的这种一连一大片的结果呢?仔细看下截图可以发现,由于取的是定位标记的外部包围盒的顶点,所以因为误差会超出定位标记的范围,导致没能正确定位到 Timing Pattern ,而是相邻的 Data 部分的像素点。为了修正这部分误差,我们可以对端点坐标进行调整。因为 Position Detection Pattern 的大小是固定的,是一个 1-1-3-1-1 的黑白黑白黑相间的正方形,识别 Timing Pattern 的最佳端点应该是最靠里的黑色区域的中心位置,也就是图中的绿色虚线部分:所以我们需要对端点坐标进行调整。调整方式是,将一个端点的 x 和 y 值向另一个端点的 x 和 y 值靠近 1/14 个单位距离,代码如下:a1 = (a1[0] + (a2[0]-a1[0])*1/14, a1[1] + (a2[1]-a1[1])*1/14)b1 = (b1[0] + (b2[0]-b1[0])*1/14, b1[1] + (b2[1]-b1[1])*1/14)a2 = (a2[0] + (a1[0]-a2[0])*1/14, a2[1] + (a1[1]-a2[1])*1/14)b2 = (b2[0] + (b1[0]-b2[0])*1/14, b2[1] + (b1[1]-b2[1])*1/14)调整之后的像素值就是正确的 Timing Pattern 了:[ 255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. & &0. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255. &255.]验证是否是 Timing Pattern像素序列拿到了,接下来就是判断它是否是 Timing Pattern 了。 Timing Pattern 的特征是黑白均匀相间,所以每段同色区域的计数结果应该相同,而且旋转拉伸平移都不会影响这个特征。于是,验证方案是:先除去数组中开头和结尾处连续的白色像素点。对数组中的元素进行计数,相邻的元素如果值相同则合并到计数结果中。比如 [0,1,1,1,0,0] 的计数结果就是 [1,3,2] 。计数数组的长度如果小于 5 ,则不是 Timing Pattern 。计算计数数组的方差,看看分布是否离散,如果方差大于阈值,则不是 Timing Pattern 。代码如下:def isTimingPattern(line): & & & &while line[0] != 0: & & & &line = line[1:] & &while line[-1] != 0: & & & &line = line[:-1] & & & &c = [] & &count = 1 & &l = line[0] & &for p in line[1:]: & & & &if p == l: & & & & & &count = count + 1 & & & &else: & & & & & &c.append(count) & & & & & &count = 1 & & & &l = p & &c.append(count) & & & &if len(c) < 5: & & & &return False & & & &threshold = 5 & &return np.var(c) < threshold对前面的那条连线检测一下,计数数组为:[11, 12, 11, 12, 11, 12, 11, 13, 11]方差为 0.47 。其他非 Timing Pattern 的连线方差均大于 10 。找出错误的定位标记接下来就是利用前面的结果除去错误的定位标记了,只要两个定位标记的端点连线中能找到 Timing Pattern ,则这两个定位标记有效,把它们存进 set 里:valid = set()for i in range(len(boxes)): & &for j in range(i+1, len(boxes)): & & & &if check(boxes[i], boxes[j]): & & & & & &valid.add(i) & & & & & &valid.add(j)print valid结果是:set([1, 2, 3])好了,它们中出了一个叛徒,0、1、2、3 四个定位标记,0是无效的,1、2、3 才是需要识别的 QRCode 的定位标记。找出二维码有了定位标记之后,找出二维码就轻而易举了。只要找出三个定位标记轮廓的最小矩形包围盒,那就是二维码的位置了:contour_all = np.array([])while len(valid) > 0: & &c = found[valid.pop()] & &for sublist in c: & & & &for p in sublist: & & & & & &contour_all.append(p)rect = cv2.minAreaRect(contour_ALL)box = cv2.boxPoints(rect)box = np.array(box)draw_img = img.copy()cv2.polylines(draw_img, np.int32([box]), True, (0, 0, 255), 10)show(draw_img)绘制结果如下:小结后面仿射变换后坐标修正的问题实在是写不动了,这篇就先到这里吧。回头看看,是不是感觉绕了个大圈子?『费了半天劲,只是为了告诉我第0个定位标记是无效的,我看图也看出来了啊!』是的,不过代码里能看到的只是像素值和它们的坐标,为了排除这个错误答案确实花了不少功夫。不过这也是我喜欢做数字图像处理的原因之一:可用函数数不胜数,专业概念层出不穷,同样的一个问题,不同的人去解决,就有着不同的答案,交流的过程便是学习的过程。啊对了,如果有更好的解决方案,欢迎在评论里指出!以及,文章里有一个红包彩蛋,你找到了吗 =。=财富!名誉!地位!穷得叮当响的海贼汪,哥尔·D·汪海,他在临睡前的一句话让人们趋之若鹜奔向博客:『想要我的红包吗?想要的话可以全部给你,去找吧!我把所有红包都放在那里!』参考文献:)
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量5550点击量4715点击量4657点击量4065点击量4039点击量3947点击量3829点击量3406点击量3312
&2016 Chukong Technologies,Inc.
京公网安备89

我要回帖

更多关于 opencv mat 数组 的文章

 

随机推荐