在OpenCV中查找凸性缺陷? [根据给定的输入图像会崩溃..]

5
我有一个计算图像凸包的程序。我试图使用此信息来计算输入图像中存在的手指数量。通过一些搜索,我发现计算手指数量的方法是:
  1. 找到轮廓
  2. 凸包
  3. 凸缺陷
但我在使用凸缺陷函数时遇到了问题。它可以编译,但在运行时,程序会崩溃某些输入图像,而对于其他图像则不会,我似乎无法弄清楚原因。
这些是输入图像:
  1. this 图像导致崩溃
  2. this 不会。
  3. this 尽管与上述相似,但也会导致崩溃
代码...
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv/cxcore.h>
#include <stdio.h>

#define CVX_RED     CV_RGB(0xff,0x00,0x00)
#define CVX_GREEN   CV_RGB(0x00,0xff,0x00)
#define CVX_BLUE    CV_RGB(0x00,0x00,0xff)

int main(int argc, char* argv[]) {

  cvNamedWindow( "original", 1 );
  cvNamedWindow( "contours", 1 );
  cvNamedWindow( "hull", 1 );
  IplImage* original_img = NULL;

  original_img = cvLoadImage("img.jpg", CV_LOAD_IMAGE_GRAYSCALE );

  IplImage* img_edge = cvCreateImage( cvGetSize(original_img), 8, 1 );
  IplImage* contour_img = cvCreateImage( cvGetSize(original_img), 8, 3 );
  IplImage* hull_img = cvCreateImage( cvGetSize(original_img), 8, 3 );

  cvThreshold( original_img, img_edge, 128, 255, CV_THRESH_BINARY );

  CvMemStorage* storage = cvCreateMemStorage();
  CvSeq* first_contour = NULL;

  int Nc = cvFindContours(
     img_edge,
     storage,
     &first_contour,
     sizeof(CvContour),
     CV_RETR_LIST // Try all four values and see what happens
  );

  for( CvSeq* c=first_contour; c!=NULL; c=c->h_next ) {
     cvCvtColor( original_img, contour_img, CV_GRAY2BGR );
     cvDrawContours(
        contour_img,
        c,
        CVX_RED,  
        CVX_BLUE,
        0,     
        2,
        8
     );
  }

  //----------------------------------------------------------------------Convex Hull

  CvMemStorage* hull_storage = cvCreateMemStorage();
  CvSeq* retHulls = NULL;

  for(CvSeq* i = first_contour; i != NULL; i = i->h_next){
    retHulls = cvConvexHull2(i,hull_storage,CV_CLOCKWISE,0); 
    // with 1 it draws the Hull image but not with 0..?
    // however it needs to be 0 for convexitydefects to work?
  }

  printf(" %d elements:\n", retHulls->total );

  // drawing hull

  for( CvSeq* j=retHulls; j!=NULL; j=j->h_next ) {
    cvCvtColor( original_img, hull_img, CV_GRAY2BGR );
    cvDrawContours(
        hull_img,
        j,
        CVX_RED,  
        CVX_BLUE,
        0,        
        2,
        8
     );  

  }


  //----------------------------------------------------------------------Convexity Defects??

  CvMemStorage* convexStorage = cvCreateMemStorage();
  CvSeq* defect = NULL;
  defect = cvConvexityDefects(first_contour,retHulls, convexStorage);
  printf(" %d defect:\n", defect->total );


  cvShowImage( "contours", contour_img );
  cvShowImage( "original", original_img );
  cvShowImage( "hull", hull_img );
  cvWaitKey(0);
  cvDestroyWindow( "contours" );
  cvDestroyWindow( "original" );
  cvDestroyWindow( "hull" );
  cvReleaseImage( &original_img );
  cvReleaseImage( &contour_img );
  cvReleaseImage( &hull_img );
  cvReleaseImage( &img_edge );
  return 0;
}

你尝试过使用C++接口来检查问题是否仍然存在,或者必须使用C吗?只是问一下,因为你也标记了C++。 - Sassa
@Bob 你好,我认为凸性函数没有C++接口。我已经添加了视图标签。 - silent
2
cv::convexHull和cv::convexityDefects在2.4.2版本中存在(http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=convexhull#structural-analysis-and-shape-descriptors)。但我不知道2.3版本是否有。 - Sassa
1
@RuiMarques 请记住,C++ API 在背后使用 C API 来完成一些工作。 ;) 升级可能无法解决问题,但值得尝试。 - karlphillip
2
在一个奇怪的应用程序中(使用OpenCV 2.4.x),有一个使用C++ API的示例,可以在以下链接中找到:https://github.com/v2lab/n0things-app/blob/master/Constantijn/ImageProcessing.mm#L118 - artm
显示剩余2条评论
4个回答

3

使用存在问题的图像运行应用程序会导致其冻结,但在OpenCV 2.4.2中没有崩溃,问题实际上发生在cvConvexityDefects()处,根据gdb的结果:

(gdb) bt
#0  0x00000001002b1491 in cvConvexityDefects ()
#1  0x0000000100001a8d in main ()

我不能告诉你为什么。由于参数似乎没问题,你可能想在这里注册一个新问题


我目前正在使用OpenCV 2.3,您是说在2.4.2下所有图像都能正常工作吗?还是仍然存在与有问题的图像相同的问题? - silent
2
在2.4.2版本中,你的程序在cvConvexityDefects()处挂起而不是崩溃。这只是与你观察到的不同的行为,但仍表明问题存在。 - karlphillip

3

cvConvexityDefects函数期望第二个参数convexHull是第一个参数contour的索引序列:

使用ConvexHull2获得的凸包应该包含轮廓点的指针或索引,而不是凸包点本身

  1. 在最简单的情况下,即cvFindContours返回单个简单轮廓(您的第二张图像),您的代码会将正确的序列作为第一个参数提供。

  2. 如果cvFindContours在轮廓中发现孔(您的第三张图像),或者如果有多个简单轮廓或带孔的轮廓(您的第一张图像),您的代码:

    1. 依次找到每个轮廓的凸包,但仅记住最后一个(因为循环的每次迭代都会覆盖retHulls变量)

    2. 将整个轮廓层次结构传递给cvConvexityDefects作为第一个参数,这与retHulls中的索引不对应。

相反,您应该:

  1. CV_RETR_EXTERNAL传递给cvFindContour,以仅获取外部轮廓(您不关心孔的缺陷)

  2. cvConvexityDefects移动到最后一个循环内。

例如:

  /* ... */

  if (argc < 2) {
      std::cerr << "Usage: convexity IMAGE\n";
      exit(1);
  }

  cvNamedWindow( "original", 1 );
  cvNamedWindow( "contours", 1 );
  cvNamedWindow( "hull", 1 );
  IplImage* original_img = NULL;

  original_img = cvLoadImage(argv[1], CV_LOAD_IMAGE_GRAYSCALE );

  IplImage* img_edge = cvCreateImage( cvGetSize(original_img), 8, 1 );
  IplImage* contour_img = cvCreateImage( cvGetSize(original_img), 8, 3 );
  IplImage* hull_img = cvCreateImage( cvGetSize(original_img), 8, 3 );

  cvThreshold( original_img, img_edge, 128, 255, CV_THRESH_BINARY );

  CvMemStorage* storage = cvCreateMemStorage();
  CvSeq* first_contour = NULL;

  int Nc = cvFindContours(
     img_edge,
     storage,
     &first_contour,
     sizeof(CvContour),
     CV_RETR_EXTERNAL // Try all four values and see what happens
  );

  cvCvtColor( original_img, contour_img, CV_GRAY2BGR );
  for( CvSeq* c=first_contour; c!=NULL; c=c->h_next ) {
     cvDrawContours(
        contour_img,
        c,
        CVX_RED,
        CVX_BLUE,
        0,
        2,
        8
     );
  }
  cvShowImage( "contours", contour_img );

  //----------------------------------------------------------------------Convex Hull
  //-------------------------------------------------------------------Convex Defects

  CvMemStorage* hull_storage = cvCreateMemStorage();
  CvSeq* retHulls = NULL;

  cvCvtColor( original_img, hull_img, CV_GRAY2BGR );
  for(CvSeq* i = first_contour; i != NULL; i = i->h_next){
    retHulls = cvConvexHull2(i,hull_storage,CV_CLOCKWISE,0);
    printf(" %d elements:\n", retHulls->total );

    CvSeq* defect = NULL;
    defect = cvConvexityDefects(i,retHulls, NULL); // reuse storage of the contour
    printf(" %d defect:\n", defect->total );

    // drawing hull.... you can't use the one returned above since it only
    // contains indices
    retHulls = cvConvexHull2(i,hull_storage,CV_CLOCKWISE,1);
    cvDrawContours(
        hull_img,
        retHulls,
        CVX_RED,
        CVX_BLUE,
        0,
        2,
        8
     );
  }

  cvShowImage( "hull", hull_img );
  /* ... */

2
不管这样解决问题是否成功,事实是函数在使用无效参数时不应崩溃或冻结。这是一个真正的问题,应该在OpenCV的问题跟踪器上得到解决。您的回答很好,等到问题提出者测试后我会给您点赞的。 - karlphillip
我同意,OpenCV 经常文档不全,出错时要么默默失败,要么会报一些晦涩难懂的错误。有一半的时间我只能通过尝试和错误来弄清楚细节,而另外一半则是通过仔细阅读源代码来解决问题。 - artm

2
使用Python 3和OpenCV 4.5.1时,我遇到了一个类似的问题,即从convexHull()返回的索引“不是单调的”。
我发现由于某种原因,这个函数返回的索引是无序的。
为了解决这个问题,在将索引传递给convexityDefects()之前,我只需将numpy数组按降序排序即可。
hull = cv2.convexHull(contours, returnPoints=False)
hull[::-1].sort(axis=0)
defects = cv2.convexityDefects(contours, hull)

1
我之前遇到过这个问题,当时我使用了OpenCV 4.4.0中的MAT,我猜测崩溃是由于这个错误引起的。
“凸包索引不是单调的,在'convexityDefects'函数中可能出现这种情况,当输入轮廓包含自相交时。”
解决方案很简单,只需详细阅读崩溃报告即可。问题在于索引没有按顺序排序,可能是因为轮廓中存在一些自相交(或者可能是一个故障,因为版本2.1修复了版本3.2后又回到了4.4.0,请参见:
https://github.com/opencv/opencv/issues/4539 所以为了不再对这个问题感到困惑,我已经在从轮廓提取缺陷之前重新排序了凸包索引本身。
正如您所看到的,凸包索引被颠倒排序,如果凸包单元格大小为6,则该单元格中的索引将为:
[0] = 5
[1] = 4
[2] = 3
[3] = 2
[4] = 1
[5] = 0

但是由于交叉路口或其他原因,它可能无法按照应有的方式进行排序,例如:
[0] = 6
[1] = 5
[2] = 4
[3] = 2
[4] = 1
[5] = 0

所以我们需要做的就是重新排序它 检查这段代码
vector <vector <Vec4i> > defects(contour.size());  // Defects Vectors
vector <vector <cv::Point> > hullsP(contour.size()); // Hulls contour points
vector <vector <int> > hullsI(contour.size()); // Indices to hulls contour points

for(int i = 0; i < contour.size(); i++)
{
    convexHull(contour[i], hullsP[i], CV_CLOCKWISE, false);
    convexHull(contour[i], hullsI[i], CV_CLOCKWISE, false);
    
    for(size_t k =0; k < hullsI[i].size(); k++) //Here we resort the indices
    {
        if (hullsI[i].size() > 0)
        {
            hullsI[i][k] = (int)((hullsI[i].size() - k)-1);
        }
    }
    
    if(hullsI[i].size() > 3 ) // You need more than 3 indices
    {
        convexityDefects(contour[i], hullsI[i], defects[i]);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接