C++?OpenCV實(shí)現(xiàn)物體尺寸測(cè)量示例詳解
前言
本文將使用OpenCV C++ 進(jìn)行物體尺寸測(cè)量。具體來說就是先定位到待測(cè)物體的位置,然后測(cè)量物體的寬高。
一、圖像透視矯正
原圖如圖所示。本案例的需求是測(cè)量圖片中兩張卡片的尺寸。首先,我們得定位到兩張卡片的位置。第一步,我們首先得將白色A4紙切割出來,這樣方便定位到兩張卡片所在位置。這里用到的算法是圖像透視矯正,具體可以參考OpenCV C++案例實(shí)戰(zhàn)四《圖像透視矯正》
//圖像矯正 void getWarp(Mat src, Mat &Warp) { Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat thresh; threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU); Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5)); Mat open; morphologyEx(thresh, open, MORPH_OPEN, kernel); vector<vector<Point>>contours; findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector<vector<Point>>conPoly(contours.size()); vector<Point>srcPts; //找到最大輪廓 int MaxIndex = 0; double Area = 0; for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area > Area) { Area = area; MaxIndex = i; } } //獲取矩形四個(gè)角點(diǎn) double peri = arcLength(contours[MaxIndex], true); approxPolyDP(contours[MaxIndex], conPoly[MaxIndex], 0.02*peri, true); srcPts = { conPoly[MaxIndex][0],conPoly[MaxIndex][1],conPoly[MaxIndex][2],conPoly[MaxIndex][3] }; int T_L, B_L, B_R, T_R; int width = src.cols / 2; int height = src.rows / 2; for (int i = 0; i < srcPts.size(); i++) { if (srcPts[i].x < width && srcPts[i].y < height) { T_L = i; } if (srcPts[i].x < width && srcPts[i].y > height) { B_L = i; } if (srcPts[i].x > width && srcPts[i].y > height) { B_R = i; } if (srcPts[i].x > width && srcPts[i].y < height) { T_R = i; } } double UpWidth = EuDis(srcPts[T_L], srcPts[T_R]); double DownWidth = EuDis(srcPts[B_L], srcPts[B_R]); double MaxWidth = max(UpWidth, DownWidth); double UpHeight = EuDis(srcPts[T_L], srcPts[B_L]); double DownHeight = EuDis(srcPts[T_R], srcPts[B_R]); double MaxHeight = max(UpHeight, DownHeight); //透視變換進(jìn)行圖像矯正 Point2f SrcAffinePts[4] = { Point2f(srcPts[T_L]),Point2f(srcPts[T_R]) ,Point2f(srcPts[B_R]) ,Point2f(srcPts[B_L]) }; Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(MaxWidth,0),Point2f(MaxWidth,MaxHeight),Point2f(0,MaxHeight) }; Mat M = getPerspectiveTransform(SrcAffinePts, DstAffinePts); warpPerspective(src, Warp, M, Point(MaxWidth, MaxHeight)); }
效果如圖所示。接下來,我們需要定位兩張卡片所在位置,尋找特征。
二、物體定位
//獲取物體坐標(biāo) void FindPts(Mat &Warp, vector<vector<Point>>&TargetPts) { Mat gray; cvtColor(Warp, gray, COLOR_BGR2GRAY); Mat thresh; threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3)); Mat open; morphologyEx(thresh, open, MORPH_OPEN, kernel); vector<vector<Point>>contours; findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector<vector<Point>>conPoly(contours.size()); //定位卡片四個(gè)角點(diǎn) for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area > 1000) { double peri = arcLength(contours[i], true); approxPolyDP(contours[i], conPoly[i], 0.02*peri, true); vector<Point>temp; temp = { conPoly[i][0],conPoly[i][1], conPoly[i][2], conPoly[i][3] }; TargetPts.push_back(temp); } } }
如圖所示。通過上面代碼段,我們已經(jīng)定位出卡片的四個(gè)角點(diǎn)。接下來,只需根據(jù)角點(diǎn)位置就可以計(jì)算卡片的寬高了。
三、尺寸測(cè)量
//計(jì)算距離 void DrawAndCompute(Mat &Warp, vector<vector<Point>>&TargetPts) { for (int i = 0; i < TargetPts.size(); i++) { for (int j = 0; j < TargetPts[i].size(); j++) { //尺寸測(cè)量 Point PtA = Point(TargetPts[i][j]); Point PtB = Point(TargetPts[i][(j + 1) % TargetPts[i].size()]); double dis = round(EuDis(PtA, PtB) * 100) / 100; //效果顯示 circle(Warp, TargetPts[i][j], 5, Scalar(0, 255, 0), -1); line(Warp, PtA, PtB, Scalar(0, 0, 255), 2); char text[20]; sprintf_s(text, "%.2f", dis); Point point = Point((PtA.x + PtB.x) / 2, (PtA.y + PtB.y) / 2); putText(Warp, text, point, FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 0, 255), 2); } } }
四、效果顯示
五、源碼
#include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; //歐式距離 double EuDis(Point pt1, Point pt2) { return sqrt((pt2.x - pt1.x)*(pt2.x - pt1.x) + (pt2.y - pt1.y)*(pt2.y - pt1.y)); } //圖像矯正 void getWarp(Mat src, Mat &Warp) { Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat thresh; threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU); Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5)); Mat open; morphologyEx(thresh, open, MORPH_OPEN, kernel); vector<vector<Point>>contours; findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector<vector<Point>>conPoly(contours.size()); vector<Point>srcPts; //找到最大輪廓 int MaxIndex = 0; double Area = 0; for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area > Area) { Area = area; MaxIndex = i; } } //獲取矩形四個(gè)角點(diǎn) double peri = arcLength(contours[MaxIndex], true); approxPolyDP(contours[MaxIndex], conPoly[MaxIndex], 0.02*peri, true); srcPts = { conPoly[MaxIndex][0],conPoly[MaxIndex][1],conPoly[MaxIndex][2],conPoly[MaxIndex][3] }; int T_L, B_L, B_R, T_R; int width = src.cols / 2; int height = src.rows / 2; for (int i = 0; i < srcPts.size(); i++) { if (srcPts[i].x < width && srcPts[i].y < height) { T_L = i; } if (srcPts[i].x < width && srcPts[i].y > height) { B_L = i; } if (srcPts[i].x > width && srcPts[i].y > height) { B_R = i; } if (srcPts[i].x > width && srcPts[i].y < height) { T_R = i; } } double UpWidth = EuDis(srcPts[T_L], srcPts[T_R]); double DownWidth = EuDis(srcPts[B_L], srcPts[B_R]); double MaxWidth = max(UpWidth, DownWidth); double UpHeight = EuDis(srcPts[T_L], srcPts[B_L]); double DownHeight = EuDis(srcPts[T_R], srcPts[B_R]); double MaxHeight = max(UpHeight, DownHeight); //透視變換進(jìn)行圖像矯正 Point2f SrcAffinePts[4] = { Point2f(srcPts[T_L]),Point2f(srcPts[T_R]) ,Point2f(srcPts[B_R]) ,Point2f(srcPts[B_L]) }; Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(MaxWidth,0),Point2f(MaxWidth,MaxHeight),Point2f(0,MaxHeight) }; Mat M = getPerspectiveTransform(SrcAffinePts, DstAffinePts); warpPerspective(src, Warp, M, Point(MaxWidth, MaxHeight)); } //獲取物體坐標(biāo) void FindPts(Mat &Warp, vector<vector<Point>>&TargetPts) { Mat gray; cvtColor(Warp, gray, COLOR_BGR2GRAY); Mat thresh; threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3)); Mat open; morphologyEx(thresh, open, MORPH_OPEN, kernel); vector<vector<Point>>contours; findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector<vector<Point>>conPoly(contours.size()); //定位卡片四個(gè)角點(diǎn) for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area > 1000) { double peri = arcLength(contours[i], true); approxPolyDP(contours[i], conPoly[i], 0.02*peri, true); vector<Point>temp; temp = { conPoly[i][0],conPoly[i][1], conPoly[i][2], conPoly[i][3] }; TargetPts.push_back(temp); } } } //計(jì)算距離 void DrawAndCompute(Mat &Warp, vector<vector<Point>>&TargetPts) { for (int i = 0; i < TargetPts.size(); i++) { for (int j = 0; j < TargetPts[i].size(); j++) { //尺寸測(cè)量 Point PtA = Point(TargetPts[i][j]); Point PtB = Point(TargetPts[i][(j + 1) % TargetPts[i].size()]); double dis = round(EuDis(PtA, PtB) * 100) / 100; //效果顯示 circle(Warp, TargetPts[i][j], 5, Scalar(0, 255, 0), -1); line(Warp, PtA, PtB, Scalar(0, 0, 255), 2); char text[20]; sprintf_s(text, "%.2f", dis); Point point = Point((PtA.x + PtB.x) / 2, (PtA.y + PtB.y) / 2); putText(Warp, text, point, FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 0, 255), 2); } } } int main() { Mat src = imread("src.jpg"); if (src.empty()) { cout << "No Image!" << endl; system("pause"); return -1; } Mat Warp; getWarp(src, Warp); vector<vector<Point>>TargetPts; FindPts(Warp, TargetPts); DrawAndCompute(Warp, TargetPts); imshow("Warp", Warp); waitKey(0); destroyAllWindows(); system("pause"); return 0; }
總結(jié)
本文使用OpenCV C++ 進(jìn)行物體尺寸測(cè)量,關(guān)鍵步驟有以下幾點(diǎn)。
1、圖像透視矯正。方便定位物體所在位置。
2、物體定位。定位所需物體位置,獲取特征。
3、根據(jù)已知特征進(jìn)行計(jì)算。
以上就是C++ OpenCV實(shí)現(xiàn)物體尺寸測(cè)量示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C++ OpenCV物體尺寸測(cè)量的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c++11之std::async 和std::thread的區(qū)別小結(jié)
std::async和std::thread都是C++11中提供的線程庫,它們都可以用于創(chuàng)建新線程,本文主要介紹了c++11之std::async 和std::thread的區(qū)別小結(jié),感興趣的可以了解一下2024-02-02C++ 中使用lambda代替 unique_ptr 的Deleter的方法
這篇文章主要介紹了C++ 中使用lambda代替 unique_ptr 的Deleter的方法,需要的朋友可以參考下2017-04-04C++中Overload,Override,Hide之間的區(qū)別
重載overload,這個(gè)概念是大家熟知的。在同一可訪問區(qū)內(nèi)被聲名的幾個(gè)具有不同參數(shù)列的(參數(shù)的類型、個(gè)數(shù)、順序不同)同名函數(shù),程序會(huì)根據(jù)不同的參數(shù)列來確定具體調(diào)用哪個(gè)函數(shù),這種機(jī)制就是重載2013-09-09C++中調(diào)用復(fù)制(拷貝)函數(shù)的三種情況總結(jié)
這篇文章主要介紹了C++中調(diào)用復(fù)制(拷貝)函數(shù)的三種情況總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11C語言中函數(shù)棧幀的創(chuàng)建和銷毀的深層分析
在C語言中,每一個(gè)正在運(yùn)行的函數(shù)都有一個(gè)棧幀與其對(duì)應(yīng),棧幀中存儲(chǔ)的是該函數(shù)的返回地址和局部變量。從邏輯上講,棧幀就是一個(gè)函數(shù)執(zhí)行的環(huán)境:函數(shù)參數(shù)、函數(shù)的局部變量、函數(shù)執(zhí)行完后返回到哪里等等2022-04-04