OpenCV中文网站

 找回密码
 立即注册
搜索
热搜: 安装 配置
查看: 5613|回复: 6

Opencv4Android分享:寻找已知标记

[复制链接]
发表于 2016-7-12 22:20:04 | 显示全部楼层 |阅读模式
本帖最后由 mumumusuc 于 2016-7-12 23:24 编辑

    上篇:Opencv4Android分享:OpenCV与JNI/NDK
    本片内容主要是寻找和匹配一种已知标记,完成对对象的分类、跟踪和信息处理等。
    你可以在《深入理解OpenCV 实用计算机视觉项目解析》这本书里找到IOS上的相关代码,这篇文章也只是把这本书上的代码移植到Android平台。

一 .效果图
MarkTrack.gif sample1.gif



二 .C++实现
    先在VS2015+OpenCV2.4.13上验证算法。
    直接借用书上5x5的标记,其对应的信息为
  1. uchar _code[] = { 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1 };
复制代码
   简单的来说,如果检测到一个矩形标记并且携带信息与给定标记相同,则能标记处这个物体的位置、景深、方向和携带数据等信息,如第二张图就在检测到的标记上绘制了一个立方体。先从静态图片开始:
1.提取标记轮廓
    使用矩形标记的很大原因就是方便检测,并且不会受到旋转等因素的干扰。提取轮廓的过程为常用的一套:
灰度化--滤波--阈值化--提取轮廓--排除无用的轮廓--提取轮廓矩形的4个定点坐标

  1.         //灰度化
  2.         cvtColor(origin, _src, CV_RGB2GRAY);
  3.         //滤波
  4.         //GaussianBlur(_src, _src, Size(5, 5), 0);
  5.         //阈值化
  6.         //threshold(_src, _src, 125, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
  7.         adaptiveThreshold(_src, _src, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 13, 5);
  8.         //轮廓
  9.         vector<vector<Point> > contours;
  10.         vector<Vec4i> hierarchy;
  11.         findContours(_src, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
  12.         //检测
  13.         vector<vector<Point> > polys;
  14.         vector<Point> poly;
  15.         float minLength = 100*ratio;
  16.         for (int i = 0; i < hierarchy.size(); i++){
  17.     approxPolyDP(contours[i], poly, 5.f, true);                        //对提取的轮廓进行多边形逼近筛选
  18.     if (poly.size() != 4) continue;                                         //只要4个顶点的轮廓
  19.     if (!isContourConvex(poly)) continue;                              //只要凸多边形
  20.     if (getDistance(poly[1], poly[0]) < minLength) continue;     //逼近多边形边长要大于最小值
  21.     if (getDistance(poly[3], poly[0]) < minLength) continue;
  22.     polys.push_back(poly);                                                 //把符合要求的轮廓顶点放到序列当中
  23.         }
复制代码
根据使用环境调整参数取值,比如图像大小、环境光线等等。

注意到标记应该是检查目标的最小单元:即目标标记应该不再包含其他标记。上面我只把小于最小边长的轮廓筛选出来了,对于更大的轮廓却没有进行处理。

新建位图图像.png <-筛选前,筛选后-> 新建位图图像.png


2.处理标记轮廓

    假设你已经获取了一系列待处理的矩形轮廓的顶点坐标,这些坐标的起点、顺序都是不同的。为了接下来的比对查找,应当使这些顶点顺序排列。这里我希望它们逆时针排列:

  1. Point t;
  2.         for(int i=0;i<polys.size();i++)
  3.     if(!isAntiClockWise(polys[i][0],polys[i][1],polys[i][3])){
  4.         t = polys[i][1];
  5.         polys[i][1] = polys[i][3];
  6.         polys[i][3] = t;
  7.                 }
复制代码
  1. bool isAntiClockWise(Point o,Point a,Point b){
  2.     Point oa = Point(a.x - o.x,a.y - o.y);
  3.     Point ob = Point(b.x - o.x,b.y - o.y);
  4.     if(oa.x*ob.y - oa.y*ob.x < 0)
  5.         return true;
  6.     return false;
  7. }
复制代码
   利用逆时针排列向量积z分量小于0这一特点判别是否逆时针排列(由于图像坐标Y轴向下,所以向量积<0)。

    在Log中查看一下顶点坐标:

  1. cout << polys[i] << endl;
复制代码
  1. [249, 387;
  2.   232, 583;
  3.   440, 673;
  4.   495, 461]
复制代码

3.删除透视投影,对比给定标记
    获取到标记4个顶点坐标后将要用getPerspectiveTransform和warpPerspective获取标记的正视图。用法如下:
  1. Mat m = getPerspectiveTransform(_poly, _rect);
  2.                 Mat temp;
  3.                 warpPerspective(origin, temp, m, Size(700, 700));
复制代码
   由于getPerspectiveTransform接受的是vector<oint2f>型参数,要把vector<oint>的poly转成vector<oint2f>型的_poly。
    同样定义一个逆时针顶点的矩形域来作为变换顶点产生投影矩阵m。
  1. vector<Point2f> _rect;
  2.                 _rect.push_back(Point2f(0, 0));
  3.                 _rect.push_back(Point2f(0, 700));
  4.                 _rect.push_back(Point2f(700, 700));
  5.                 _rect.push_back(Point2f(700, 0));
复制代码
   利用warpPerspective函数把标记区域投影到一个700x700的矩形图中,如果之前的处理正确的话你应该获得了如下的视图之一:

新建位图图像.png 新建位图图像.png 新建位图图像.png 新建位图图像.png
    出去边缘留白,有用的信息在中心5x5的矩形中,对比给定标记的信息即可得知是否为所需标记和其正确方向。流程如下:
灰度化--阈值化--解码--对比--旋转--对比--找到正确方向
  1. cvtColor(temp, temp, CV_RGBA2GRAY);
  2.                 threshold(temp, temp, 125, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
  3.                 //解码
  4.                 Mat ecode = Mat(Size(5, 5), CV_8U);
  5.                 Mat sub;
  6.                 Rect rect;
  7.                 for (int i = 0; i < 5; i++){
  8.     for (int k = 0; k < 5; k++){
  9.         rect = Rect(100 + k * 100, 100 + i * 100, 100, 100);
  10.         sub = temp(rect);
  11.         ecode.at<uchar>(i, k) = countNonZero(sub)>5000*ratio ? 1 : 0;    //用countNonZero(Mat)将白色区域编码为1,黑色区域为0.
  12.     }
  13.                 }
  14.                 int index = 0;
  15.                 bool found = false;
  16.                 for (int i = 0; i < 4; i++){
  17.     if (equals(markCode, ecode)){
  18.         index = (4 - i) % 4;
  19.         found = true;
  20.         break;
  21.     }
  22.     else
  23.         ecode = getRotateMat(ecode);
  24.                 }
  25.                 if (!found) continue;        
复制代码
   如果这个就是正确的标记的话,经过3次旋转一定会有一个完全相等的状态!最后就是要把检测到的标记直观的绘制出来:
  1.                 Point A = Point(polys[j][index].x/ratio,polys[j][index].y/ratio);
  2.                 Point B = Point(polys[j][(index+1)%4].x/ratio,polys[j][(index+1)%4].y/ratio);
  3.                 Point C = Point(polys[j][(index+2)%4].x/ratio,polys[j][(index+2)%4].y/ratio);
  4.                 Point D = Point(polys[j][(index+3)%4].x/ratio,polys[j][(index+3)%4].y/ratio);
复制代码
   效果图:
新建位图图像.png

三 . 接下来还能做什么?
    如果按照《深入理解OpenCV 实用计算机视觉项目解析》这本书的流程,接下来就是AR场景渲染了。我在Android上用OpenGL试了一下,简单的画了个立方体,效果如下:
QQ图片20160712224121.png
    或者像我一开始给出的效果图那样,对相机的每一帧进行这样从处理,从而实现实时AR渲染。流程如下:
相机内参数标定--实时外参数计算--根据内参数、外参数矩阵改变GL的投影和模型矩阵
    不过这些都是题外话了,说不定以后有机会能细说

四 .还需要做什么?
    事实上,这里才是关键问题所在:在嵌入式平台(android、ios或其他)上,这种低成本的图像处理真的可行吗?
    之前说到实验平台是VS2015+opencv2.4.13,在楼主i7-4720+GTX960M的硬件下,以上未经优化的代码跑一边大概需要100ms左右。如果输入图像拥有多个目标标记或者需要检测多种标记,再或者需要24FPS实时处理,这样还是远远不够的。
    换到Android上又如何呢?楼主的实验平台为Nexus5X,骁龙808+Adreno 418 GPU,测试成绩如下:
log.bmp
    1920x1080分辨率下,大部分时间全花在寻找标记上,平均用时177ms,平均帧数5FPS,根本是不可用的状态。在接收原图片时进行缩放降低像素是一种解决办法,代价是顶点坐标会更不准确(应该用亚像素角点)。那么还有其他解决方案吗?
    OpenCV有GPU加速的方案,但是需要CUDA。OpenCL呢?早在android4.3 google就已经移除了OpenCL相关的so。注意到以上算法中含有大量的循环运算和循环查找,异步计算仍然是最终方案。幸好android上还有RenderScript,运算结果如下:
新建位图图像.bmp
    貌似稍微快了一点呢
    总结一下:嵌入式做视觉处理嘛,低成本就意味着高优化难度,OpenCV还是留着验证算法的好。
回复

使用道具 举报

发表于 2016-7-15 20:56:06 | 显示全部楼层
不错,谢谢分享。收藏了
回复 支持 反对

使用道具 举报

发表于 2016-7-18 14:19:48 | 显示全部楼层
楼主,能加上QQ交流一下么,396214143.  我是的产品涉及到类似的技术
回复 支持 反对

使用道具 举报

发表于 2016-7-18 16:10:43 | 显示全部楼层
先mark  
回复 支持 反对

使用道具 举报

发表于 2016-7-19 10:15:20 | 显示全部楼层
多谢分享
回复 支持 反对

使用道具 举报

发表于 2016-7-19 11:20:42 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2017-3-29 14:29:42 | 显示全部楼层
你好,能发下vs版本的源码吗,谢谢楼主!772952260@qq.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|OpenCV中文网站

GMT+8, 2024-4-20 03:01 , Processed in 0.012010 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表