OpenCV開發筆記(五十六):紅胖子8分鐘帶你深入瞭解多種圖形擬合逼近輪廓(圖文並茂+淺顯易懂+程序源碼)

若該文爲原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106180872
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)

OpenCV開發專欄(點擊傳送門)

上一篇:《OpenCV開發筆記(五十五):紅胖子8分鐘帶你深入瞭解Haar、LBP特徵以及級聯分類器識別過程(圖文並茂+淺顯易懂+程序源碼)
下一篇:持續補充中…


前言

  紅胖子,來也!
  識別目標,可以通過圖形擬合,將目標提取出來。


Demo

  在這裏插入圖片描述
  在這裏插入圖片描述
  在這裏插入圖片描述
  在這裏插入圖片描述
   在這裏插入圖片描述
  在這裏插入圖片描述


尋找輪廓

  參考博文《OpenCV開發筆記(四十九):紅胖子8分鐘帶你深入瞭解輪廓識別(圖文並茂+淺顯易懂+程序源碼)


尋找輪廓凸包

  參考博文《OpenCV開發筆記(五十):紅胖子8分鐘帶你深入瞭解輪廓凸包(圖文並茂+淺顯易懂+程序源碼)


對輪廓凸包進行多圖形擬合

概述

  尋找輪廓之後,openCV提供了對輸入點集合進行多種圖形進行擬合的方法,基本都是輸入之前尋找凸包後再進行操作,當然也可以直接對了輪廓進行操作。
  識別不同的目標物體,根據形狀可以剔除,還可以做很多其他的操作,比如車牌識別,提取車牌號碼,那麼直接可以拿到每個車牌字符的矩形,直接對矩形進行roi,然後在進行下一步的識別操作(補充:這部分可以拿到座標後,自己寫算法也是一樣,看個人習慣)。

返回包圍的矩形函數原型

  (返回的是水平的矩形)

Rect boundingRect( InputArray points );
  • 參數一:InputArray類型的points,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中;

返回包圍的最小面積矩形函數原型

(最小面積則其返回的矩形基本都是旋轉的,注意返回的類型)

RotatedRect minAreaRect( InputArray points );
  • 參數一:InputArray類型的points,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中;

返回包圍的圓形函數原型

void minEnclosingCircle( InputArray points,
                         Point2f& center,
                         float& radius );
  • 參數一:InputArray類型的points,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中;
  • 參數二:Point2f類型的center,返回圓形的中心點;
  • 參數三:float類型的radius,返回圓形的半徑;

返回包圍的最小橢圓函數原型

  (注意:至少需要輸入6個點)

RotatedRect fitEllipse( InputArray points );
  • 參數一:InputArray類型的points,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中(至少要6個點);
      返回提取四個點的代碼:
cv::RotatedRect rotateRect = cv::minAreaRect(hullPoints);
cv::Point2f vertex[4];
rotateRect.points(vertex);

返回包圍的多邊形擬合函數原型

void approxPolyDP( InputArray curve,
                   OutputArray approxCurve,
                   double epsilon,
                   bool closed );
  • 參數一:InputArray類型的curve,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中;
  • 參數二:OutputArray類型的approxCurve;輸出多邊形結果std::vector<cv::Point2f>;
  • 參數三:double類型的epsilon,指定近似精度。這是最大距離;
  • 參數四:bool類型的closed,如果爲真,則近似曲線是閉合的(其第一個頂點和最後一個頂點是已連接)。否則,它不會關閉。

返回包圍的最小三角形函數原型

double minEnclosingTriangle( InputArray points, OutputArray triangle );
  • 參數一:InputArray類型的points,二維點(輪廓頂點)的輪廓輸入向量,存儲在std::vector或Mat中;
  • 參數二:OutputArray類型triangle,返回三角形;

Demo源碼

void OpenCVManager::testFitting()
{
    QString fileName1 =
            "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/10.jpg";
    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    cv::Mat dstMat;
    int width = 400;
    int height = 300;

    cv::resize(srcMat, srcMat, cv::Size(width, height));

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 3,
                                         srcMat.rows * 4),
                                srcMat.type());
    int sigmaS = 100;
    int sigmaR = 1.0;

    int thresh = 232;
    int maxval = 255;

    while(true)
    {
        // 刷新全圖黑色
        windowMat = cv::Scalar(0, 0, 0);

        // 原圖複製
        cv::Mat mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
        cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);

        cv::Mat tempMat;
        {
            {
                cvui::printf(windowMat, 75 + width * 1, 40 + height * 0, "sigmaS");
                cvui::trackbar(windowMat, 75 + width * 1, 50 + height * 0, 165, &sigmaS, 101, 10000);
                cvui::printf(windowMat, 75 + width * 1, 90 + height * 0, "sigmaR");
                cvui::trackbar(windowMat, 75 + width * 1, 100, 165 + height * 0, &sigmaR, 1, 100);

                // 使用自適應流形應用高維濾波。
                cv::Ptr<cv::ximgproc::AdaptiveManifoldFilter> pAdaptiveManifoldFilter
                        = cv::ximgproc::createAMFilter(sigmaS/100.0f, sigmaR/100.0f, true);
                pAdaptiveManifoldFilter->filter(srcMat, tempMat);
                // 效果圖copy
                mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, tempMat, 1.0f, 0.0f, mat);
            }

            //  轉爲灰度圖像
            cv::cvtColor(tempMat, tempMat, cv::COLOR_BGR2GRAY);

            // 車牌時,對灰度圖取反操作
//            tempMat = ~tempMat;

            {
                // 調整閾值化的參數thresh
                cvui::printf(windowMat, 75 + width * 1, 20 + height * 1, "thresh");
                cvui::trackbar(windowMat, 75 + width * 1, 40 + height * 1, 165, &thresh, 0, 255);
                // 調整閾值化的參數maxval
                cvui::printf(windowMat, 75 + width * 1, 80 + height * 1, "maxval");
                cvui::trackbar(windowMat, 75 + width * 1, 100 + height * 1, 165, &maxval, 0, 255);

                // 閾值化,注意:此處使用了THRESH_BINARY_INV,白色是255,255,255所以反轉閾值化
                cv::threshold(tempMat, tempMat, thresh, maxval, cv::THRESH_BINARY_INV);
                // 效果圖copy
                mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
                                cv::Range(srcMat.cols * 2, srcMat.cols * 3));

                //  轉換圖像
                cv::Mat grayMat;
                cv::cvtColor(tempMat, grayMat, cv::COLOR_GRAY2BGR);
                cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
            }

            // 尋找輪廓
            {
                std::vector<std::vector<cv::Point>> contours;
                std::vector<cv::Vec4i> hierarchy;
                // 查找輪廓:RETR_EXTERNAL-最外層輪廓
                cv::findContours(tempMat, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
                // 遍歷所有頂層輪廓,並繪製出來
                dstMat = srcMat.clone();
                cv::Mat emptyMat = srcMat.clone();
                emptyMat = cv::Scalar(0,0,0);

                // 擬合矩形框
                cv::Mat fittingRectMat = srcMat.clone();
                cv::Mat fittingMinAreaRectMat = srcMat.clone();
                cv::Mat fittingMinAreaCircleMat = srcMat.clone();
                cv::Mat fittingEllipseMat = srcMat.clone();
                cv::Mat fittingPolyMat = srcMat.clone();

                // 輪廓contours[i]對應4個hierarchy元素hierarchy[i][0]~ hierarchy[i][3],
                // hierarchy[i][0]表示後一個輪廓的索引編號
                // hierarchy[i][1]前一個輪廓的索引編號
                // hierarchy[i][2]父輪廓的索引編號
                // hierarchy[i][3]內嵌輪廓的索引編號
                for(int index = 0; index >=0; index = hierarchy[index][0])
                {

                    if(hierarchy.size() <= 0)
                    {
                        break;
                    }
                    cv::Scalar color;
                    if(index < hierarchy.size() / 3)
                    {
                        color = cv::Scalar(250 / (hierarchy.size() / 3) * index, 125, 255);
                    }else if(index < hierarchy.size() / 3 * 2)
                    {
                        color = cv::Scalar(255, 250 / (hierarchy.size() / 3) * (index - hierarchy.size() / 3), 125);
                    }else
                    {
                        color = cv::Scalar(125, 255, 250 / (hierarchy.size() / 3 == 0 ? 1 : 
                                                                              hierarchy.size() / 3) * (index - hierarchy.size() / 3 * 2));
                    }
                    // 繪製輪廓裏面的第幾個
                    cv::drawContours(emptyMat, contours, index, color, CV_FILLED, 8, hierarchy);

                    // 尋找最大凸包
                    std::vector<cv::Point> hullPoints;
                    std::vector<int> hullIndex;
                    cv::convexHull(contours[index], hullPoints, false, true);
                    cv::convexHull(contours[index], hullIndex, false, false);
                    // 繪製凸包包圍線
                    for(int index2 = 1; index2 < hullPoints.size(); index2++)
                    {
                        cv::line(mat, hullPoints.at(index2 - 1), hullPoints.at(index2), cv::Scalar(0, 0, 0), 2);
                        cv::line(dstMat, hullPoints.at(index2 - 1), hullPoints.at(index2), cv::Scalar(0, 0, 0), 2);
                    }
                    qDebug() << __FILE__ << __LINE__ << "index =" << index << "total =" << hierarchy.size();


                    // 使用形狀擬合
                    // 使用外部包圍矩形
                    {
                        cv::Rect rect = cv::boundingRect(hullPoints);
                        cv::rectangle(fittingRectMat, rect, cv::Scalar(0, 255, 0), 2);
                    }
                    // 使用外部最小包圍矩形
                    {
                        cv::RotatedRect rotateRect = cv::minAreaRect(hullPoints);
                        cv::Point2f vertex[4];
                        rotateRect.points(vertex);
                        for(int index = 0; index < 4; index++)
                        {
                            cv::line(fittingMinAreaRectMat, vertex[index % 4], vertex[(index + 1) % 4], cv::Scalar(255, 0, 0), 2);
                        }
                    }
                    // 使用外部包圍圓形(圓形就是最小了,不存在形變)
                    {
                        cv::Point2f center;
                        float radius;
                        cv::minEnclosingCircle(hullPoints, center, radius);
                        cv::circle(fittingMinAreaCircleMat, center, radius, cv::Scalar(0, 0, 0), 2);
                    }
                    // 使用外部橢圓擬合:至少要6個點
                    {
                        qDebug() << __FILE__ << __LINE__ << hullPoints.size();
                        if(hullPoints.size() >= 6)
                        {
                            cv::RotatedRect rotateRect = cv::fitEllipse(hullPoints);
                            cv::ellipse(fittingEllipseMat, rotateRect, cv::Scalar(0, 0, 0), 2);
                        }
                    }
                    // 使用多邊形擬合
                    {
                        std::vector<cv::Point> polyPoints;
                        cv::approxPolyDP(hullPoints, polyPoints, 3, true);
                        std::vector<std::vector<cv::Point>> contour;
                        contour.push_back(polyPoints);
                        cv::drawContours(fittingPolyMat, contour, 0, cv::Scalar(0, 0, 0), 2);
                    }
                }

                // 效果圖copy:輪廓圖
                mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                                cv::Range(srcMat.cols * 2, srcMat.cols * 3));
                cv::addWeighted(mat, 0.0f, emptyMat, 1.0f, 0.0f, mat);
                // 效果圖copy:對已知輪廓進行最大凸包檢測
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
                // 效果圖copy:黑色圖擬合矩形
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 1, srcMat.cols * 2));
                cv::addWeighted(mat, 0.0f, fittingRectMat, 1.0f, 0.0f, mat);
                // 效果圖copy:原圖擬合矩形
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 1, srcMat.cols * 2));
                cv::addWeighted(mat, 0.0f, fittingRectMat, 1.0f, 0.0f, mat);
                // 效果圖copy:原圖擬合最小矩形
                mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                                cv::Range(srcMat.cols * 2, srcMat.cols * 3));
                cv::addWeighted(mat, 0.0f, fittingMinAreaRectMat, 1.0f, 0.0f, mat);

                // 效果圖copy:原圖擬合最小圓形
                mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
                                cv::Range(srcMat.cols * 0, srcMat.cols * 1));
                cv::addWeighted(mat, 0.0f, fittingMinAreaCircleMat, 1.0f, 0.0f, mat);

                // 效果圖copy:原圖擬合最小橢圓
                mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
                                cv::Range(srcMat.cols * 1, srcMat.cols * 2));
                cv::addWeighted(mat, 0.0f, fittingEllipseMat, 1.0f, 0.0f, mat);

                // 效果圖copy:原圖擬合多邊形
                mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
                                cv::Range(srcMat.cols * 2, srcMat.cols * 3));
                cv::addWeighted(mat, 0.0f, fittingPolyMat, 1.0f, 0.0f, mat);
            }
        }

        // 更新
        cvui::update();
        // 顯示
        cv::imshow(windowName, windowMat);
        // esc鍵退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}

工程模板:對應版本號v1.50.0

   對應版本號v1.50.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章