最近在windows平台使用OpenCV的findContours函数时,会出现崩溃问题。
在网上搜了很多解决方案,比如:
1.配置属性->常规->项目默认值->MFC的使用->在共享DLL中使用MFC;
2.C/C++>代码生成->运行库->多线程DLL(/MD);
3.代码中vector要使用cv::vector,vector
我就不挨着列举了,相信很多小伙伴使用上述方法根本无法解决问题。或者个别小伙伴可以碰巧把问题解决,但程序的移植性肯定会存在风险。
接下来,我会提供一个真正能解决这个问题的方案。
我们要明白,为什么findContours会出现异常。
我们来看findContours的源代码:
void cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours,
OutputArray _hierarchy, int mode, int method, Point offset )
{
Mat image = _image.getMat();
MemStorage storage(cvCreateMemStorage());
CvMat _cimage = image;
CvSeq* _ccontours = 0;
if( _hierarchy.needed() )
_hierarchy.clear();
cvFindContours(&_cimage, storage, &_ccontours, sizeof(CvContour), mode, method, offset);
if( !_ccontours )
{
_contours.clear();
return;
}
Seq all_contours(cvTreeToNodeSeq( _ccontours, sizeof(CvSeq), storage ));
int i, total = (int)all_contours.size();
_contours.create(total, 1, 0, -1, true);
SeqIterator it = all_contours.begin();
for( i = 0; i < total; i++, ++it )
{
CvSeq* c = *it;
((CvContour*)c)->color = (int)i;
_contours.create((int)c->total, 1, CV_32SC2, i, true);
Mat ci = _contours.getMat(i);
CV_Assert( ci.isContinuous() );
cvCvtSeqToArray(c, ci.data);
}
if( _hierarchy.needed() )
{
_hierarchy.create(1, total, CV_32SC4, -1, true);
Vec4i* hierarchy = _hierarchy.getMat().ptr();
it = all_contours.begin();
for( i = 0; i < total; i++, ++it )
{
CvSeq* c = *it;
int h_next = c->h_next ? ((CvContour*)c->h_next)->color : -1;
int h_prev = c->h_prev ? ((CvContour*)c->h_prev)->color : -1;
int v_next = c->v_next ? ((CvContour*)c->v_next)->color : -1;
int v_prev = c->v_prev ? ((CvContour*)c->v_prev)->color : -1;
hierarchy[i] = Vec4i(h_next, h_prev, v_next, v_prev);
}
}
}
首先,我们可以看到,findContours内部其实调用的是cvFindContours这个函数,只不过findContours把输入输出数据结构重新封装了一遍而已。
在findContours使用异常的情况下,我测试使用cvFindContours,发现程序一切正常。这就给我增加了极大的信心。
然后,我们再仔细阅读findContours的函数,会发现以下代码:
int i, total = (int)all_contours.size();
_contours.create(total, 1, 0, -1, true);
以及
CvSeq* c = *it;
((CvContour*)c)->color = (int)i;
_contours.create((int)c->total, 1, CV_32SC2, i, true);
Mat ci = _contours.getMat(i);
cvCvtSeqToArray(c, ci.data);
我们在使用findContours的时候习惯将_contours参数写作vector
但从上述代码中我们发现,opencv在分配_contours内存空间的时候,只是粗暴地理解成动态创建数组。
尤其是在赋值点集数据的时候,opencv先将_contours中的每一个vector当做Mat数据结构,然后直接粗暴地进行数据填充。
综上,我猜测,要么是_contours.create()未必适合vector的内存分配,要么是vector的数据不能简单地理解为连续内存空间,并进行简单的数据拷贝填充。
最终导致内存被破坏,运行发生异常。
前面说了,虽然findContours使用异常,但是cvFindContours使用是完全OK的。
那么我们重新利用cvFindContours进行一次封装,规避掉前面提到的存在风险的内存操作不就行了?
废话不多说,直接贴代码。拿好不谢!
void findContours(const Mat& src, vector>& contours, vector& hierarchy,
int retr = RETR_LIST, int method = CHAIN_APPROX_SIMPLE, Point offset = Point(0, 0))
{
CvMat c_image = src;
MemStorage storage(cvCreateMemStorage());
CvSeq* _ccontours = 0;
cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint(offset));
if (!_ccontours)
{
contours.clear();
return;
}
Seq all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));
int total = (int)all_contours.size();
contours.resize(total);
SeqIterator it = all_contours.begin();
for (int i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
((CvContour*)c)->color = (int)i;
int count = (int)c->total;
int* data = new int[count * 2];
cvCvtSeqToArray(c, data);
for (int j = 0; j < count; j++)
{
contours[i].push_back(Point(data[j * 2], data[j * 2 + 1]));
}
delete[] data;
}
hierarchy.resize(total);
it = all_contours.begin();
for (int i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
int h_next = c->h_next ? ((CvContour*)c->h_next)->color : -1;
int h_prev = c->h_prev ? ((CvContour*)c->h_prev)->color : -1;
int v_next = c->v_next ? ((CvContour*)c->v_next)->color : -1;
int v_prev = c->v_prev ? ((CvContour*)c->v_prev)->color : -1;
hierarchy[i] = Vec4i(h_next, h_prev, v_next, v_prev);
}
storage.release();
}
这个是我2019年2月26日第一篇原创博客,如果要转载,请标明出处,爱你们。