OpenCV開發筆記(五十六):紅胖子8分鐘帶你深入了解多種圖形擬合逼近輪廓(圖文並茂+淺顯易懂+程式源碼)
若該文為原創文章,未經允許不得轉載
原部落客部落格地址://blog.csdn.net/qq21497936
原部落客部落格導航://blog.csdn.net/qq21497936/article/details/102478062
本文章部落格地址://blog.csdn.net/qq21497936/article/details/106180872
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)
紅胖子,來也!
識別目標,可以通過圖形擬合,將目標提取出來。
尋找輪廓之後,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,返回三角形;
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