天天看點

OpenCV findContours函數崩潰的真正有效解決方案

最近在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日第一篇原創部落格,如果要轉載,請标明出處,愛你們。