最近在windows平台使用OpenCV的findContours函數時,會出現崩潰問題。
在網上搜了很多解決方案,比如:
1.配置屬性->正常->項目預設值->MFC的使用->在共享DLL中使用MFC;
2.C/C++>代碼生成->運作庫->多線程DLL(/MD);
3.代碼中vector要使用cv::vector,vector<vector>要事先配置設定空間;
我就不挨着列舉了,相信很多小夥伴使用上述方法根本無法解決問題。或者個别小夥伴可以碰巧把問題解決,但程式的移植性肯定會存在風險。
接下來,我會提供一個真正能解決這個問題的方案。
問題原因
我們要明白,為什麼findContours會出現異常。
我們來看findContours的源代碼:
void cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours,
OutputArray _hierarchy, int mode, int method, Point offset )
{
CV_INSTRUMENT_REGION();
// Sanity check: output must be of type vector<vector<Point>>
CV_Assert((_contours.kind() == _InputArray::STD_VECTOR_VECTOR || _contours.kind() == _InputArray::STD_VECTOR_MAT ||
_contours.kind() == _InputArray::STD_VECTOR_UMAT));
CV_Assert(_contours.empty() || (_contours.channels() == 2 && _contours.depth() == CV_32S));
Mat image0 = _image.getMat(), image;
Point offset0(0, 0);
if(method != CV_LINK_RUNS)
{
offset0 = Point(-1, -1);
copyMakeBorder(image0, image, 1, 1, 1, 1, BORDER_CONSTANT | BORDER_ISOLATED, Scalar(0));
}
else
{
image = image0;
}
MemStorage storage(cvCreateMemStorage());
CvMat _cimage = cvMat(image);
CvSeq* _ccontours = 0;
if( _hierarchy.needed() )
_hierarchy.clear();
cvFindContours_Impl(&_cimage, storage, &_ccontours, sizeof(CvContour), mode, method, cvPoint(offset0 + offset), 0);
if( !_ccontours )
{
_contours.clear();
return;
}
Seq<CvSeq*> all_contours(cvTreeToNodeSeq( _ccontours, sizeof(CvSeq), storage ));
int i, total = (int)all_contours.size();
_contours.create(total, 1, 0, -1, true);
SeqIterator<CvSeq*> 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.ptr());
}
if( _hierarchy.needed() )
{
_hierarchy.create(1, total, CV_32SC4, -1, true);
Vec4i* hierarchy = _hierarchy.getMat().ptr<Vec4i>();
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<vector>類型。
但從上述代碼中我們發現,opencv在配置設定_contours記憶體空間的時候,隻是粗暴地了解成動态建立數組。
尤其是在指派點集資料的時候,opencv先将_contours中的每一個vector當做Mat資料結構,然後直接粗暴地進行資料填充。
綜上,我猜測,要麼是_contours.create()未必适合vector的記憶體配置設定,要麼是vector的資料不能簡單地了解為連續記憶體空間,并進行簡單的資料拷貝填充。
最終導緻記憶體被破壞,運作發生異常。
解決辦法
前面說了,雖然findContours使用異常,但是cvFindContours使用是完全OK的。
那麼我們重新利用cvFindContours進行一次封裝,規避掉前面提到的存在風險的記憶體操作不就行了?
廢話不多說,直接貼代碼。拿好不謝!
void findContours(const cv::Mat& src, std::vector<std::vector<cv::Point>>& contours, std::vector<cv::Vec4i>& hierarchy, int retr, int method, cv::Point offset)
{
#if CV_VERSION_REVISION <= 6
CvMat c_image = src;
#else
CvMat c_image;
c_image = cvMat(src.rows, src.cols, src.type(), src.data);
c_image.step = src.step[0];
c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (src.flags & cv::Mat::CONTINUOUS_FLAG);
#endif
cv::MemStorage storage(cvCreateMemStorage());
CvSeq* _ccontours = nullptr;
#if CV_VERSION_REVISION <= 6
cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint(offset));
#else
cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint{ offset.x, offset.y });
#endif
if (!_ccontours)
{
contours.clear();
return;
}
cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));
size_t total = all_contours.size();
contours.resize(total);
cv::SeqIterator<CvSeq*> it = all_contours.begin();
for (size_t i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i);
int count = c->total;
int* data = new int[static_cast<size_t>(count * 2)];
cvCvtSeqToArray(c, data);
for (int j = 0; j < count; j++)
contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1]));
delete[] data;
}
hierarchy.resize(total);
it = all_contours.begin();
for (size_t i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
int h_next = c->h_next ? reinterpret_cast<CvContour*>(c->h_next)->color : -1;
int h_prev = c->h_prev ? reinterpret_cast<CvContour*>(c->h_prev)->color : -1;
int v_next = c->v_next ? reinterpret_cast<CvContour*>(c->v_next)->color : -1;
int v_prev = c->v_prev ? reinterpret_cast<CvContour*>(c->v_prev)->color : -1;
hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev);
}
storage.release();
}
聲明
這個是我2019年2月26日第一篇原創部落格,如果要轉載,請标明出處,愛你們。