《实用OpenCV》<六> 图像中的形状(1)

        形状是当我们看到物体时最开始的印象之一,这一章我们将赋予计算机这种能力。识别图像里的形状是通常是做决策时一个重要步骤。形状是由图像的轮廓形成的,所以理论上形状识别是通常在边缘或轮廓检测后的步骤。
        所以,我们将首先讨论从图像里提取轮廓,然后再开始讨论形状。将会包含:

           •霍夫变换,可以使我们检测图像里的常规形状如线条和圆形。

           •随机样本一致性(RANSAC),一个广泛使用的可以确定数据点来匹配特定模型的框架。我们将编写算法代码来检测图像里的椭圆。

           •对象周围的绑定盒,绑定椭圆和凸形外壳的计算。

           •形状匹配。

轮廓
        轮廓和边缘有一个显著区别。边缘是图像亮度梯度的局部极大值集合。我们也看到,这些梯度极大值不全是在物体的轮廓上而且他们是非常有噪声的。Canny边缘有一点不同,更像轮廓一些,因为在梯度极大值提取后经过一些后处理步骤。轮廓,相对而言,是一系列相连的点,更可能落在物体的外框上。
        OpenCV的轮廓提取基于二值图像(像Canny边缘检测的输出或对Scharr边缘做阈值处理或者一张黑白图)然后提取边缘点连接的层次结构。组织层次使得位于数结构更高的轮廓更有可能是物体的轮廓,然而低位的轮廓更有可能是噪声边缘和“洞口”的轮廓以及噪声块。
        实现这些特性的函数叫findContours()然后它使用了由S.Suzuki和K.Abe在“数字二值图像的基于边界跟随的拓扑结构分析”一文中描述的算法来提取轮廓并排列层次结构。文中描述了决定层次结构的详细规则,简而言之呢,当一个轮廓围绕着另一个轮廓的时候被认为是那个轮廓的“父亲”。
        为了更实际地显示我们所说的层次结构呢,我们将编写一个程序,见例6-1,使用了我们最喜欢的工具,滑块,来选择要显示层次结构的级别值。注意该函数仅接受一个二值图像为输入。从普通图像得到二值图的方式有:
         •通过threshold()或adaptiveThreshold()来阈值处理
        •使用inRange()检查像素值边界
        •Canny边缘
        •Scharr边缘做阈值处理


例 6-1 程序展现层次轮廓提取

// Program to illustrate hierarchical contour extraction
// Author: Samarth Manoj Brahmbhatt, University of Pennsylvania
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
Mat img;
vector<vector<Point> > contours;
vector<Vec4i> heirarchy;
int levels = 0;
void on_trackbar(int, void *) {
  if(contours.empty()) return;
  Mat img_show = img.clone();
  // Draw contours of the level indicated by slider
  drawContours(img_show, contours, -1, Scalar(0, 0, 255), 3, 8, heirarchy, levels);
  imshow("Contours", img_show);
}
int main() {
  img = imread("circles.jpg");
  Mat img_b;
  cvtColor(img, img_b, CV_RGB2GRAY);
  Mat edges;
  Canny(img_b, edges, 50, 100);
  // Extract contours and heirarchy
  findContours(edges, contours, heirarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
  namedWindow("Contours");
  createTrackbar("levels", "Contours", &levels, 15, on_trackbar);
  // Initialize by drawing the top level contours (as 'levels' is initialized to 0)
  on_trackbar(0, 0);
  while(char(waitKey(1)) != 'q') {}
  return 0;
}
        注意每个轮廓是一个STL向量里的点。所以,储存轮廓的数据结构是一个含点向量的向量。层次结构是一个含四整数向量的向量(注:即向量的元素也是向量)。对每个轮廓来说,它的层次结构位置表示为四个整数值:他们是轮廓向量基于0的索引分别指示 下一位置(同等级),上一个(同等级),父级,以及第一个子轮廓。假使其中任意一个不存在(比如,如果一个轮廓没有父轮廓),对应的整数值则为负值。同时注意drawContours()函数根据层次结构和绘制允许最大层次等级来通过绘制轮廓修改输入图片。

图6-1显示了一张简图上的不同等级的轮廓。


                                                                            

   图6-1  不同等级的轮廓层次

        经常和findContours()一起使用的一个函数是approxPolyDP()。approxPolyDP()用另一条顶点较少的曲线来逼近一条曲线或者一个多边形,这样两条曲线之间的距离小于或等于指定的精度。同时也有使闭合逼近曲线的选项(那就是说,起始点和终止点相同)

点-多边形测试
        我们先暂且来介绍一个有趣的特性:点-多边形测试。你也许猜到了,pointPolygonTest()函数判定一个点是否在一个多边形内。如果你开启 measureDist标签的话它也会返回该点到轮廓最近点的有符号欧式距离。如果点在曲线内,距离则为正,外则负,点在轮廓上则为零。如果标签关闭的话,相应的距离则被替换为+1,-1和0。
 让我们来做一个程序来演示点-多边形和闭合曲线逼近的新知识——一个寻找图像上用户点击点相近的最小闭合轮廓的程序。同时它也演示了轮廓层次的导引。代码见例6-2

例6-2 寻找点击点围绕的最小轮廓

// Program to find the smallest contour that surrounds the clicked point
// Author: Samarth Manoj Brahmbhatt, University of Pennsylvania
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
Mat img_all_contours;
vector<vector<Point> > closed_contours;
vector<Vec4i> heirarchy;
// Function to approximate contours by closed contours
vector<vector<Point> > make_contours_closed(vector<vector<Point> > contours) {
   vector<vector<Point> > closed_contours;
   closed_contours.resize(contours.size());
   for(int i = 0; i < contours.size(); i++)
   approxPolyDP(contours[i], closed_contours[i], 0.1, true);
   return closed_contours;
}
// Function to return the index of smallest contour in 'closed_contours' surrounding the clicked point
int smallest_contour(Point p, vector<vector<Point> > contours, vector<Vec4i> heirarchy) {
   int idx = 0, prev_idx = -1;
   while(idx >= 0) {
   vector<Point> c = contours[idx];
   // Point-polgon test
   double d = pointPolygonTest(c, p, false);
   // If point is inside the contour, check its children for an even smaller contour...
   if(d > 0) {
     prev_idx = idx;
     idx = heirarchy[idx][2];
    }
    // ...else, check the next contour on the same level
    else idx = heirarchy[idx][0];
  }
  return prev_idx;
}
void on_mouse(int event, int x, int y, int, void *) {
   if(event != EVENT_LBUTTONDOWN) return;
   // Clicked point
   Point p(x, y);
   // Find index of smallest enclosing contour
   int contour_show_idx = smallest_contour(p, closed_contours, heirarchy);
   // If no such contour, user clicked outside all contours, hence clear image
   if(contour_show_idx < 0) {
   imshow("Contours", img_all_contours);
   return;
   }
   // Draw the smallest contour using a thick red line
   vector<vector<Point> > contour_show;
   contour_show.push_back(closed_contours[contour_show_idx]);
   if(!contour_show.empty()) {
      Mat img_show = img_all_contours.clone();
      drawContours(img_show, contour_show, -1, Scalar(0, 0, 255), 3);
      imshow("Contours", img_show);
   }
}
int main() {
   Mat img = imread("circles.jpg");
   img_all_contours = img.clone();
   Mat img_b;
   cvtColor(img, img_b, CV_RGB2GRAY);
   Mat edges;
   Canny(img_b, edges, 50, 100);
   // Extract contours and heirarchy
   vector<vector<Point> > contours;
   findContours(edges, contours, heirarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
   // Make contours closed so point-polygon test is valid
   closed_contours = make_contours_closed(contours);
   // Draw all contours usign a thin green line
   drawContours(img_all_contours, closed_contours, -1, Scalar(0, 255, 0));
   imshow("Contours", img_all_contours);
   // Mouse callback
   setMouseCallback("Contours", on_mouse);
   while(char(waitKey(1)) != 'q') {}
   return 0;
}
       假设 idx为轮廓在点向量的向量中的索引而hierarchy代表层次的话:
          • hierarchy[idx][0] 返回同等级层次结构的下一个轮廓索引
          • hierarchy[idx][1]  返回同等级层次结构的上一个轮廓索引
          • hierarchy[idx][2]  返回第一个子轮廓的索引
          • hierarchy[idx][3]  返回父轮廓的索引
       如果其中一个轮廓不存在,返回索引为负值。
程序运行的截图如图6-2所示


                                                                  

       图 6-2 最小封闭轮廓的应用

        OpenCV也提供了一些函数检查其中的一些属性来帮助你过滤噪声图像的轮廓。如表6-1


 表6-1 OpenCV轮廓后处理函数

函数        描述
ArcLength()  查找轮廓长度
ContourArea()  查找轮廓区域和方向
BoundingRect()计算轮廓的垂直边界矩形
ConvexHull()    计算轮廓围绕的凸形壳
IsContourConvex()  测试轮廓的凸性
MinAreaRect()  计算围绕轮廓的最小旋转矩形
MinEnclosingCircle()  查找围绕轮廓的最小区域圆形
FitLine()    基于轮廓匹配一条线(最小二乘)


  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值