【opencv】第8章 圖像輪廓與圖像分割修復

8.1 查找并繪制輪廓

一個輪廓一般對應一系列的點,也就是圖像中的一條曲線。其表示方法可能 根據不同的情況而有所不同。在OpenCV 中,可以用findContours()函數從二值圖 像中查找輪廓

8.1.1 尋找輪廓: findContours() 函數

findContours) 函數用于在二值圖像中尋找輪廓。

void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, intmethod, Point offset = Point())
  • 第一個參數,InputArray類型的image, 輸入圖像,即源圖像,填Mat 類的 對象即可,且需為8位單通道圖像。圖像的非零像素被視為1,0像素值被 保留為0,所以圖像為二進制。我們可以使用 compare() 、inrange()、
    threshold() 、adaptivethreshold() 、cannyO 等函數由灰度圖或彩色圖創建二進 制圖像。此函數會在提取圖像輪廓的同時修改圖像的內容。
  • 第二個參數,OutputArrayOfArrays 類 型 的contours、檢測到的輪廓、函數 調用后的運算結果存在這里。每個輪廓存儲為一個點向量,即用point 類 型 的 vector表示。
  • 第三個參數,OutputArray 類型的hierarchy,可選的輸出向量,包含圖像的 拓撲信息。其作為輪廓數量的表示,包含了許多元素。每個輪廓contours[i] 對 應 4 個hierarchy 元 素hierarchy[i][0]~hierarchy[i][3], 分別表示后一 個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。如果沒有對應項,
    對應的hierarchy[i] 值設置為負數。
  • 第四個參數,int 類型的mode, 輪廓檢索模式,取值如表8.1所示。

表8.1 findContours函數可選的輪廓檢索模式

標識符含義
RETR_EXTERNAL表示只檢測最外層輪廓。對所有輪廓,設置 hierarchy[i][2]=hierarchy[i][3]=-1
RETR_LIST提取所有輪廓,并且放置在list中。檢測的輪廓 不建立等級關系
RETR_CCOMP提取所有輪廓,并且將其組織為雙層結構(two-level hierarchy:頂層為連通域的外圍邊界, 次層為孔的內層邊界
RETR_TREE提取所有輪廓,并重新建立網狀的輪廓結構
  • 第五個參數,int類 型 的method, 為輪廓的近似辦法,取值如表8.2所示。

表8.2 findContours函數可選的輪廓近似辦法

標識符含義
CHAIN_APPROX NONE獲取每個輪廓的每個像素,相鄰的兩個點的像素位置差不超過 1,即max(abs(xl-x2),abs(y2-y1))=1
CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向 的終點坐標,例如一個矩形輪廓只需4個點來保存輪廓信息
CHAIN_APPROX_TC89_L1 ,CHAIN_APPROX_TC89_KCOS使用Teh-Chinl鏈逼近算法中的一個

同樣地,在表8.1和8.2中列出的宏之前加上”CV_” 前綴,便是OpenCV2 中可以使用的宏。如"RETR_CCOMP” 宏 的OpenCV2 版 為“CV_RETR_CCOMP"。

  • 第六個參數,Point 類 型 的 offset,每個輪廓點的可選偏移量,有默認值 Point()。對 ROI 圖像中找出的輪廓,并要在整個圖像中進行分析時,這個 參數便可排上用場。

findContours 經 常 與drawContours 配合使用一使用用findContours (函數檢測 到圖像的輪廓后,便可以用drawContours (函數將檢測到的輪廓繪制出來。接下來, 讓我們一起看看drawContours() 函數的用法。

8.1.2 繪 制 輪 廓 :drawContours ()函 數

drawContours() 函數用于在圖像中繪制外部或內部輪廓。

void drawContours(InputoutputArray image, InputArrayofArrays contours, int contourIdx, const Scalar &color, int thickness = 1, int lineType = 8, InputArray hierarchy = noArray(0), int maxLevel = INT_MAX, Point offset = Point())
  • 第 一 個參數,InputArray類 型 的image, 目標圖像,填Mat 類的對象即可。
  • 第二個參數,InputArrayOfArrays類型的contours,所有的輸入輪廓。每個 輪廓存儲為一個點向量,即用point類 型 的vector表示。
  • 第三個參數,int類 型 的contourldx,輪廓繪制的指示變量。如果其為負值, 則繪制所有輪廓。
  • 第四個參數,const Scalar&類型的color, 輪廓的顏色。
  • 第五個參數,int thickness,輪廓線條的粗細度,有默認值1。如果其為負 值(如 thickness=cv_filled), 便會繪制在輪廓的內部。可選為FILLED 宏(OpenCV2版為CV_FILLED)。
  • 第六個參數,int 類型的lineType,線條的類型,有默認值8。取值類型如 表8 . 3所示。

表8.3 可選線性

lineType線性含義
8(默認值)8連通線型
44連通線型
LINE_AA(OpenCV2版為CV_AA)抗鋸齒線型
  • 第七個參數,InputArray 類型的hierarchy,可選的層次結構信息,有默認 值noArray()。
  • 第八個參數,int類型的maxLevel,表示用于繪制輪廓的最大等級,有默認 值INT_MAX
  • 第九個參數,Point類型的offset,可選的輪廓偏移參數,用指定的偏移量 offset=(dx,dy) 偏移需要繪制的輪廓,有默認值Point()。

下面是 一 個調用小示例。 //在白色圖像上繪制黑色輪廓

Mat result(image.size(),CV_8U,cv::Scalar(255));
drawContours(result,contours,- 1,Scalar(0),3);

8.1.3 基礎示例程序:輪廓查找

void Test52() {Mat srcImage = imread("image.jpg", 0); //灰度圖讀入imshow("src", srcImage);Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);srcImage = srcImage > 119;imshow("mid", srcImage); //取閾值后的圖像//定義輪廓和層次結構std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierachy;//查找輪廓findContours(srcImage, contours, hierachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);//遍歷所有頂層的羅坤,隨機顏色繪制出每個連接組件顏色int index = 0;for (; index >= 0; index = hierachy[index][0]) {Scalar color(rand() & 255, rand() & 255, rand() & 255);drawContours(dstImage, contours, index, color, FILLED, 8, hierachy);imshow("dst", dstImage);}waitKey(0);
}

在這里插入圖片描述

在這里插入圖片描述

8.1.4 綜合示例程序:查找并繪制輪廓

除了上述這個精簡版的示例程序,還為大家準備了一個更加復雜一些的關 于查找并繪制輪廓的綜合示例程序。此程序利用了圖像平滑技術(blur() 函數)和邊緣檢測技術(cannyO 函數),根據滑動條的調節,可以動態地檢測出圖形的 輪 廓 。

namespace test53 {Mat g_srcImage, g_grayImage;int g_nThresh = 80;int g_nThresh_max = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3); //Canny算子邊緣檢測findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//輪廓提取Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//隨機值drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3)); //降噪namedWindow("window1");imshow("window1", g_srcImage);createTrackbar("value", "window1", &g_nThresh, g_nThresh_max, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);}
}void Test53() {test53::Test();
}

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

8.2 尋找物體的凸包

8.2.1 凸 包

凸包(Convex Hull) 是一個計算幾何(圖形學)中常見的概念。簡單來說, 給定二維平面上的點集,凸包就是將最外層的點連接起來構成的凸多邊型,它是 能包含點集中所有點的。理解物體形狀或輪廓的一種比較有用的方法便是計算一 個物體的凸包,然后計算其凸缺陷(convexity defects)。很多復雜物體的特性能很 好地被這種缺陷表現出來。

如圖8.9所示,我們用人手圖來舉例說明凸缺陷這一概念。手周圍深色的線 描畫出了凸包,A 到 H 被標出的區域是凸包的各個“缺陷”。正如看到的,這些 凸度缺陷提供了手以及手狀態的特征表現的方法。

在這里插入圖片描述

新版OpenCV 中 ,convexHull 函數用于尋找圖像點集中的凸包,我們一起來 看一下這個函數。

8.2.2 尋找凸包:convexHullO函數

上文已經提到過,convexHullO 函數用于尋找圖像點集中的凸包,其原型聲明 如 下 。

void convexHull(InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true)
  • 第一個參數,InputArray類型的points,輸入的二維點集,可以填Mat 類型 或者std::vector。
  • 第二個參數,OutputArray類型的hull,輸出參數,函數調用后找到的凸包。
  • 第三個參數,bool 類型的clockwise,操作方向標識符。當此標識符為真時, 輸出的凸包為順時針方向。否則,就為逆時針方向。并且是假定坐標系的 x 軸指向右,y 軸指向上方。
  • 第四個參數,bool 類型的returnPoints,操作標志符,默認值true。當 標 志 符為真時,函數返回各凸包的各個點。否則,它返回凸包各點的指數。當 輸出數組是std::vector 時,此標志被忽略。

8.2.3 基礎示例程序:凸包檢測基礎

為了理解凸包檢測的運用方法,下面放出一個完整的示例程序。程序中會首 先隨機生成3~103個坐標值隨機的彩色點,然后利用convexHull, 對由這些點鏈 接起來的圖形求凸包。

void Test54() {Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();while (1) {char key;int count = (unsigned)rng % 100 + 3;std::vector<Point>points;//隨機生成坐標for (int i = 0; i < count; ++i) {Point point;point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);points.push_back(point);}//檢測凸包std::vector<int>hull;convexHull(Mat(points), hull, true);//繪制出隨機顏色的點image = Scalar::all(0);for (int i = 0; i < count; ++i) {circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),FILLED,LINE_AA);}int hullcount = hull.size();Point point0 = points[hull.back()];//依次連接凸包的點for (int i = 0; i < hullcount; ++i) {Point point = points[hull[i]];line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA);point0 = point;}imshow("hull", image);key = waitKey(0);if (key == 27) break;}}

在這里插入圖片描述

8.2.4 綜合示例程序:尋找和繪制物體的凸包

這一節的綜合示例程序,依然是結合滑動條,通過滑動條控制閾值,來得到 不同的凸包檢測效果圖。程序詳細注釋的源代碼如下。

namespace test55 {Mat g_srcImage, g_grayImage;int g_nThresh = 50;int g_maxThresh = 255;RNG g_rng(12345);Mat srcImage_copy = g_srcImage.clone();Mat g_thresholdImage_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {// 二值化threshold(g_grayImage, g_thresholdImage_output, g_nThresh, 255, THRESH_BINARY);findContours(g_thresholdImage_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //輪廓std::vector<std::vector<Point>>hull(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {convexHull(Mat(g_vContours[i]), hull[i], false);}//繪制出輪廓及凸包Mat drawing = Mat::zeros(g_thresholdImage_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));  drawContours(drawing, g_vContours, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //輪廓drawContours(drawing, hull, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //凸包}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));//降噪//創建窗口namedWindow("src");imshow("src", g_srcImage);//創建滾動條createTrackbar("value", "src", &g_nThresh, g_maxThresh, on_ThreshChange);on_ThreshChange(0, nullptr);waitKey(0);}
}void Test55() {test55::Test();
}

在這里插入圖片描述

在這里插入圖片描述

8.3 使用多邊形將輪廓包圍

在實際應用中,常常會有將檢測到的輪廓用多邊形表示出來的需求。本節就 為大家講解如何用多邊形來表示出輪廓,或者說如何根據輪廓提取出多邊形。先 讓我們一起學習用OpenCV 創建包圍輪廓的多邊形邊界時會接觸到的一些函數。

8.3.1 返回外部矩形邊界:boundingRect(O函數

此函數計算并返回指定點集最外面(up-right)的矩形邊界。 C++:Rect boundingRect(InputArray points)
其唯一的一個參數為輸入的二維點集,可以是std::vector 或 Mat 類型。

8.3.2 尋找最小包圍矩形:minAreaRect(函數

此函數用于對給定的2D 點集,尋找可旋轉的最小面積的包圍矩形。 C++:RotatedRect minAreaRect(InputArray points)
其唯一的一個參數為輸入的二維點集,可以為std::vector> 或 Mat 類型。

8.3.3尋找最小包圍圓形:minEnclosingCircle(函數

minEnclosingCircle函數的功能是利用一種迭代算法,對給定的2D 點集,去 尋找面積最小的可包圍它們的圓形。

void minEnclosingCircle(InputArray points, Point2f &center, float &radius)
  • 第 一 個 參 數 ,InputArray 類 型 的points,輸入的二維點集,可以為std::vector◇ 或Mat 類型。
  • 第二個參數,Point2f&類型的center,圓的輸出圓心。
  • 第三個參數,float&類型的radius,圓的輸出半徑。

8.3.4 用橢圓擬合二維點集:fitEllipse ( 函 數

此函數的作用是用橢圓擬合二維點集。

RotatedRect fitEllipse(InputArray points)

其唯一的一個參數為輸入的二維點集,可以為std::vector<>或Mat 類型。

8.3.5 逼近多邊形曲線: approxPolyDPO 函 數

approxPolyDP函數的作用是用指定精度逼近多邊形曲線。

void approxPolyDP(InputArray curve, OutputArray approxCurve,double epsilon, bool closed)
  • 第一個參數,InputArray類型的curve,輸入的二維點集,可以為std::vecto 或Mat 類型。
  • 第二個參數,OutputArray類型的approxCurve,多邊形逼近的結果,其類 型應該和輸入的二維點集的類型 一 致。
  • 第三個參數,double類型的epsilon,逼近的精度,為原始曲線和即近似曲 線間的最大值。
  • 第四個參數,bool 類型的closed,如果其為真,則近似的曲線為封閉曲線 (第一個頂點和最后一個頂點相連),否則,近似的曲線曲線不封閉。

8.4 圖像的矩

矩函數在圖像分析中有著廣泛的應用,如模式識別、目標分類、目標識別與 方位估計、圖像編碼與重構等。一個從一幅數字圖形中計算出來的矩集,通常描 述了該圖像形狀的全局特征,并提供了大量的關于該圖像不同類型的幾何特性信 息,比如大小、位置、方向及形狀等。圖像矩的這種特性描述能力被廣泛地應用 在各種圖像處理、計算機視覺和機器人技術領域的目標識別與方位估計中。一階 矩與形狀有關,二階矩顯示曲線圍繞直線平均值的擴展程度,三階矩則是關于平 均值的對稱性的測量。由二階矩和三階矩可以導出一組共7個不變矩。而不變矩 是圖像的統計特性,滿足平移、伸縮、旋轉均不變的不變性,在圖像識別領域得 到了廣泛的應用。

那 么 , 在OpenCV 中,如何計算 一個圖像的矩呢? 一般由 moments、 contourArea 、arcLength 這三個函數配合求取。

  • 使用moments 計算圖像所有的矩(最高到3階)
  • 使用contourArea來計算輪廓面積
  • 使用arcLength來計算輪廓或曲線長度 下面對其進行一一剖析。

8.4.1 矩的計算:momentsO函數

moments()函數用于計算多邊形和光柵形狀的最高達三階的所有矩。矩用來計 算形狀的重心、面積,主軸和其他形狀特征,如7Hu不變量等。

Moments moments(InputArray array, bool binaryImage = false)
  • 第一個參數,InputArray類型的array,輸入參數,可以是光柵圖像(單通 道,8位或浮點的二維數組)或二維數組 (IN 或 N1)。
  • 第二個參數,bool類型的binaryImage,有默認值false。若此參數取 true, 則所有非零像素為1。此參數僅對于圖像使用。
    需要注意的是,此參數的返回值返回運行后的結果。

8.4.2計算輪廓面積:contourArea(函數

contourArea()函數用于計算整個輪廓或部分輪廓的面積

double       contourArea(InputArray       contour,bool       oriented=false)
  • 第一個參數,InputArray類型的contour,輸入的向量,二維點(輪廓頂點), 可以為std::vector 或 Mat 類型。
  • 第二個參數,bool類型的oriented,面向區域標識符。若其為true,該函數 返回一個帶符號的面積值,其正負取決于輪廓的方向(順時針還是逆時針)。 根據這個特性我們可以根據面積的符號來確定輪廓的位置。需要注意的是, 這個參數有默認值false, 表示以絕對值返回,不帶符號。

8.4.3 計算輪廓長度:arcLengthO函數

arcLength(函數用于計算封閉輪廓的周長或曲線的長度。

double      arcLength(InputArray      curve,bool      closed)
  • 第一個參數,InputArray類型的curve,輸入的二維點集,可以為std:vector或Mat 類型。
  • 第二個參數,bool 類型的closed, 一個用于指示曲線是否封閉的標識符, 有默認值closed, 表示曲線封閉。

8.4.4 綜合示例程序:查找和繪制圖像輪廓矩

學習完函數的講解,讓我們一起通過一個綜合的示例程序,真正了解本節內 容的實戰用法 。

namespace test56{Mat g_srcImage, g_grayImage;int g_nThresh = 100;int g_nMaxThresh = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;//回調函數void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2); //邊緣檢測findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //找到輪廓//計算矩std::vector<Moments>mu(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mu[i] = moments(g_vContours[i], false);}//計算中心矩std::vector<Point2f>mc(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mc[i] = Point2f(static_cast<float>(mu[i].m10 / mu[i].m00), static_cast<float>(mu[i].m01 / mu[i].m00));}//繪制輪廓Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point()); //繪制內層和外層輪廓circle(drawing, mc[i], 4, color, -1, 8, 0);//繪制圓}imshow("drawing", drawing);//計算輪廓面積std::cout << "\t";for (int i = 0; i < g_vContours.size(); ++i) {std::cout << ">Through m00 Areas[" << i << "]:" << mu[i].m00 << "Length = "<< arcLength(g_vContours[i], true)<< std::endl;std::cout << ">Through OpenCV m_00 Areas[" << i << "]" <<contourArea(g_vContours[i]) <<"Length = " << arcLength(g_vContours[i], true) << std::endl;Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());circle(drawing, mc[i], 4, color, -1, 8, 0);}}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));namedWindow("srcImage");imshow("srcImage", g_srcImage);createTrackbar("value:", "srcImage", &g_nThresh, g_nMaxThresh,on_ThreshChange);waitKey(0);}}void Test56() {test56::Test();
}

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

8.5 分水嶺算法

在許多實際運用中,我們需要分割圖像,但無法從背景圖像中獲得有用信 息。分水嶺算法 (watershed algorithm) 在這方面往往是非常有效的。此算法可 以將圖像中的邊緣轉化成“山脈”,將均勻區域轉化為“山谷”,這樣有助于分 割目標。
分水嶺算法,是一種基于拓撲理論的數學形態學的分割方法,其基本思想 是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的 海拔高度,每一個局部極小值及其影響區域稱為集水盆,而集水盆的邊界則形 成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明:在每一個局部 極小值表面,刺穿一個小孔,然后把整個模型慢慢浸入水中,隨著浸入的加深, 每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構筑大壩,即 形成分水嶺。

分水嶺的計算過程是一個迭代標注過程。分水嶺比較經典的計算方法是由 L.Vincent 提出的。在該算法中,分水嶺計算分兩個步驟: 一個是排序過程, 一個是淹沒過程。首先對每個像素的灰度級進行從低到高的排序,然后在從低 到高實現淹沒的過程中,對每一個局部極小值在h 階高度的影響域采用先進先 出 (FIFO) 結構進行判斷及標注。分水嶺變換得到的是輸入圖像的集水盆圖像, 集水盆之間的邊界點,即為分水嶺。顯然,分水嶺表示的是輸入圖像的極大值 點。

也就是說,分水嶺算法首先計算灰度圖像的梯度;這對圖像中的“山谷”或 沒有紋理的“盆地”(亮度值低的點)的形成是很有效的,也對“山頭”或圖像中 有主導線段的“山脈”(山脊對應的邊緣)的形成有效。然后開始從用戶指定點(或 者算法得到點)開始持續“灌注”盆地直到這些區域連成一片。基于這樣產生的 標記就可以把區域合并到0一起,合并后的區域又通聚集的方式進行分割,好像 圖像被“填充”起來一樣。

8.5.1 實現分水嶺算法:watershedO 函 數

函 數watershed 實現的分水嶺算法是基于標記的分割算法中的 一種。在把圖像 傳給函數之前,我們需要大致勾畫標記出圖像中的期望進行分割的區域,它們被 標記為正指數。所以,每 一 個區域都會被標記為像素值1、2、3等,表示成為 一 個或者多個連接組件。這些標記的值可以使用findContours() 函 數 和drawContours()

函數由二進制的掩碼檢索出來。不難理解,這些標記就是即將繪制出來的分割區 域的“種子”,而沒有標記清楚的區域,被置為0。在函數輸出中,每 一 個標記中 的像素被設置為“種子”的值,而區域間的值被設置為- 1。

void watershed(InputArray image, InputOutputArray markers)
  • 第 一 個 參 數 ,InputArray 類 型 的src, 輸入圖像,即源圖像,填Mat 類 的 對 象
    即可,且需為8位三通道的彩色圖像。
  • 第 二 個 參 數 ,InputOutputArray 類 型 的markers, 函數調用后的運算結果存在 這里,輸入/輸出32位單通道圖像的標記結果。即這個參數用于存放函數調用后 的輸出結果,需和源圖片有 一 樣的尺寸和類型。

8.5.2 綜合示例程序:分水嶺算法

namespace test57 {Mat g_maskImage, g_srcImage;Point prevPt(-1, -1);static void on_Mouse(int event, int x, int y, int flags, void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {prevPt = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {prevPt = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (prevPt.x < 0) {prevPt = pt;}line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0);prevPt = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("mountain.jpg");imshow("srcImage", g_srcImage);Mat srcImage, grayImage;g_srcImage.copyTo(srcImage);cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);g_maskImage = Scalar::all(0);setMouseCallback("srcImage", on_Mouse, 0);while (1) {int c = waitKey(1);  // 改為 1 來更流暢地顯示if (c == 27) {break;}//恢復原圖if ((char)c == '2') {g_maskImage = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}else if ((char)c == '1') {int i, j, compCount = 0;std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierarchy;//尋找輪廓findContours(g_maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);if (contours.empty()) {continue;}Mat maskImage(g_maskImage.size(), CV_32S); //復制掩膜maskImage = Scalar::all(0);for (int index = 0; index >= 0; index = hierarchy[index][0]) {compCount++;drawContours(maskImage, contours, index, Scalar::all(compCount), -1, 8, hierarchy, INT_MAX);}if (compCount == 0) continue;//生成隨機顏色std::vector<Vec3b>colorTab;for (int i = 0; i < compCount; ++i) {uchar b = theRNG().uniform(0, 255);uchar g = theRNG().uniform(0, 255);uchar r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b(b, g, r));}watershed(srcImage, maskImage); //分水嶺算法//將分水嶺圖像遍歷存入watershedImage中Mat watershedImage(maskImage.size(), CV_8UC3);for (int i = 0; i < maskImage.rows; ++i) {for (int j = 0; j < maskImage.cols; ++j) {int index = maskImage.at<int>(i, j);if (index == -1) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0); }else if (index <= 0 || index > compCount) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);}else {watershedImage.at<Vec3b>(i, j) = colorTab[index - 1];}}}//混合灰度圖和分水嶺效果圖watershedImage = watershedImage * 0.5 + grayImage * 0.5;imshow("watershed transform", watershedImage);}}}
}void Test57() {test57::Test();
}

在這里插入圖片描述

在這里插入圖片描述

8.6 圖像修補

在實際應用中,我們的圖像常常會被噪聲腐蝕,這些噪聲或者是鏡頭上的灰 塵或水滴,或者是舊照片的劃痕,或者由于圖像的部分本身已經損壞。而“圖像 修復”(Inpainting), 就是妙手回春,解決這些問題的良方。圖像修復技術簡單來說,就是利用那些已經被破壞區域的邊緣,即邊緣的顏色和結構,繁殖和混合到 損壞的圖像中,以達到圖像修補的目的。圖8.34~8.36就是示例程序截圖,演示 將圖像中的字跡移除的效果。

8.6.1 實現圖像修補:inpaint ( 函 數

在新版OpenCV 中,圖像修補技術由inpaint 函數實現,它可以用來從掃描的 照片中清除灰塵和劃痕,或者從靜態圖像或視頻中去除不需要的物體。其原型聲 明如下。

C++:void inpaint(InputArray src,InputArray inpaintMask,OutputArray
dst,double inpaintRadius,int flags)

  • 第 一 個參數,InputArray類 型 的src, 輸入圖像,即源圖像,填Mat 類的對 象即可,且需為8位單通道或者三通道圖像。
  • 第二個參數,InputArray類型的inpaintMask, 修復掩膜,為8位的單通道 圖像。其中的非零像素表示需要修補的區域。
  • 第三個參數,OutputArray 類 型 的dst, 函數調用后的運算結果存在這里, 和源圖片有一樣的尺寸和類型。
  • 第四個參數,double 類型的 inpaintRadius,需要修補的每個點的圓形鄰域, 為修復算法的參考半徑。
  • 第五個參數,int 類型的flags,修補方法的標識符,可以是表8.4所示兩者 之一。
標識符說明
INPAINT_NS基于Navier-Stokes方程的方法
INPAINT_TELEAAlexandru Telea方法

OpenCV2 中INPAINT_NS 和INPAINT_TELEA 標識符可以分別寫作CV_ INPAINT_NS 和 CV_INPAINT_TELEA

8.6.2 綜合示例程序:圖像修補

函數和概念講解完畢,下面我們依然是學習 一 個以本節所講內容為核心的示 例程序,將本節所學內容付諸實踐,融會貫通。此示例程序會先讓我們在圖像中 用鼠標繪制出白色的線條破壞圖像,然后按下鍵盤按鍵【1】或【SPACE】 進 行 圖 像修補操作。且如果對自己的繪制不夠滿意,可以按下鍵盤按鍵【2】恢復原始圖 像。


namespace test58 {Mat g_srcImage, inpaintMask,srcImage;Point previousPoint(-1, -1);static void On_Mouse(int event,int x,int y,int flags,void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {previousPoint = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {previousPoint = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (previousPoint.x < 0) {previousPoint = pt;}line(inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, previousPoint, pt, Scalar::all(255), 5, 8, 0);previousPoint = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("sky.jpg");srcImage = g_srcImage.clone();inpaintMask = Mat::zeros(srcImage.size(), CV_8U);imshow("srcImage", g_srcImage);setMouseCallback("srcImage", On_Mouse, nullptr);while (1) {char c = waitKey(1);if (c == 27) break;if (c == '2') {inpaintMask = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}if (c == '1') {Mat inpaintedImage;inpaint(g_srcImage, inpaintMask, inpaintedImage, 3, INPAINT_TELEA); imshow("fixed", inpaintedImage);}}}}void Test58() {test58::Test();
}

在這里插入圖片描述

在這里插入圖片描述

8.7 本章小結

本章中,我們先學習了查找輪并繪制輪廓,然后學習了如何尋找到物體的凸 包,接著是使用多邊形來包圍輪廓,以及計算一個圖像的矩。在本章后面幾節, 還學習了分水嶺算法和圖像修補操作的實現方法。

函數名稱說明對應講解章節
BoundingRect計算并返回指定點集最外面(up-right)的矩形邊 界8.3.1
minAreaRect尋找可旋轉的最小面積的包圍矩形8.3.2
minEnclosingCircle利用一種迭代算法,對給定的2D點集,尋找面 積最小的可包圍他們的圓形8.3.3
fitEllipse用橢圓擬合二維點集8.3.4
approxPolyDP用指定精度逼近多邊形曲線8.3.5
moments計算多邊形和光柵形狀的最高達三階的所有矩8.4.1
contourArea計算整個輪廓或部分輪廓的面積8.4.2
arcLength計算封閉輪廓的周長或曲線的長度8.4.3
watershed實現分水嶺算法8.5.1
inpaint進行圖像修補,從掃描的照片中清除灰塵和劃痕, 或者從靜態圖像或視頻中去除不需要的物體8.6.1

本章示例程序清單

示例程序序號程序說明對應章節
69輪廓查找8.1.3
70查找并繪制輪廓8.1.4
71凸包檢測基礎8.2.3
72尋找和繪制物體的凸包8.2.4
73創建包圍輪廓的矩形邊界8.3.6
74創建包圍輪廓的圓形邊界8.3.7
75使用多邊形包圍輪廓8.3.8
76圖像輪廓矩8.4.4
77分水嶺算法的使用8.5.2
78實現圖像修補8.6.2

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/65877.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/65877.shtml
英文地址,請注明出處:http://en.pswp.cn/web/65877.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于文件系統分布式鎖原理

分布式鎖&#xff1a;在一個公共的存儲服務上打上一個標記&#xff0c;如Redis的setnx命令&#xff0c;是先到先得方式獲得鎖&#xff0c;ZooKeeper有點像下面的demo,比較大小的方式判決誰獲得鎖。 package com.ldj.mybatisflex.demo;import java.util.*; import java.util.co…

Unity 大地圖功能 離線瓦片地圖

不使用第二個攝像機實現類似開放世界的大地圖功能。 功能如下&#xff1a; 按下M鍵打開/關閉大地圖功能 打開大地圖時&#xff0c;默認玩家位置居中 大地圖支持拖拽&#xff0c;可調節拖拽速度&#xff0c;支持XY軸翻轉 支持大地圖設置邊緣偏移量 可設置是否啟動拖拽邊界 …

Bootstrap 前端 UI 框架

Bootstrap官網&#xff1a;Bootstrap中文網 鉑特優選 Bootstrap 下載 點擊進入中文文檔 點擊下載 生產文件是開發響應式網頁應用&#xff0c;源碼是底層邏輯代碼&#xff0c;因為是要制作響應式網頁&#xff0c;所以下載開發文件 引入 css 文件&#xff0c; bootstrap.css 和 …

記一次sealos部署k8s集群之delete了第一臺master如何恢復

記一次sealos部署k8s集群之delete了第一臺master如何恢復 一、背景描述 使用sealos部署了一套K8S集群 master信息:172.27.100.1、172.27.100.2、172.27.100.3 node信息:172.27.100.4、172.27.100.5 sealos安裝在172.27.100.1節點,根目錄下/root/.sealos/文件還在! [root…

error: linker `link.exe` not found

開始學習rust&#xff0c;安裝好rust的環境&#xff0c;開始從hello world開始&#xff0c;結果用在win10環境下&#xff0c;使用vs code或cmd窗口編譯rust報錯&#xff1a; PS E:\study_codes\rust-demo\chart01> rustc hello.rs error: linker link.exe not found| note:…

用 HTML5 Canvas 和 JavaScript 實現雪花飄落特效

這篇文章將帶您深入解析使用 HTML5 Canvas 和 JavaScript 實現動態雪花特效的代碼原理。 1,效果展示 該效果模擬了雪花從天而降的動態場景,具有以下特點: 雪花數量、大小、透明度和下落速度隨機。雪花會在屏幕底部重置到頂部,形成循環效果。隨窗口大小動態調整,始終覆蓋…

django基于Python的校園個人閑置物品換購平臺

Django 基于 Python 的校園個人閑置物品換購平臺 一、平臺概述 Django 基于 Python 的校園個人閑置物品換購平臺是專為校園師生打造的一個便捷、環保且充滿活力的線上交易場所。它借助 Django 這一強大的 Python Web 開發框架&#xff0c;整合了校園內豐富的閑置物品資源&…

【Vim Masterclass 筆記10】S06L23:Vim 核心操作訓練之 —— 文本的搜索、查找與替換操作(第二部分)

文章目錄 S06L23 Search, Find, and Replace - Part Two1 文本替換命令 :s/old/new/2 指定范圍的文本替換3 特例&#xff1a;路徑的替換4 文件行號的配置5 要點總結&#xff08;1&#xff09;搜索當前行&#xff08;Same Line Searching&#xff09;&#xff08;2&#xff09;跨…

【計算機網絡】課程 實驗五 靜態路由配置

實驗五 靜態路由配置 一、實驗目的 理解靜態路由的工作原理&#xff0c;掌握如何配置靜態路由。 二、實驗分析與設計 【背景描述】 假設校園網分為 2 個區域&#xff0c;每個區域內使用 1 臺路由器連接 2 個子網&#xff0c; 現要在路由器上 做適當配置&#xff0c;實現校…

Python 繼承示例:有與無 `super().__init__()` 的區別

文章目錄 Python 繼承示例&#xff1a;有與無 super().__init__() 的區別父類&#xff08;Parent&#xff09;子類&#xff08;Child&#xff09;不調用 super().__init__()子類&#xff08;Child&#xff09;調用 super().__init__() Python 繼承示例&#xff1a;有與無 super…

Linux下部署Redis(本地部署超詳細)

非docker 1、下載Redis 歷史版本&#xff1a; http://download.redis.io/releases 我的&#xff1a; http://download.redis.io/releases/redis-7.0.5.tar.gz 2.安裝教程 1.Redis是基于c語言編寫的需要安裝依賴&#xff0c;需要安裝gcc yum install gcc-c 2.查看gcc版…

Spring——幾個常用注解

環境配置 1.在配置文件中導入約束(context — 共三個)并添加一項配置( context:annotation-config/) 才能支持注解的使用 context 約束&#xff1a; xmlns:context“http://www.springframework.org/schema/context” 2.xsi:schemaLocation下的&#xff1a;" http://ww…

Oopsie【hack the box】

Oopsie 解題流程 文件上傳 首先開啟機器后&#xff0c;我們先使用 nmap -sC -SV來掃描一下IP地址&#xff1a; -sC&#xff1a;使用 Nmap 的默認腳本掃描&#xff08;通常是 NSE 腳本&#xff0c;Nmap Scripting Engine&#xff09;。這個選項會自動執行一系列常見的腳本&am…

單片機-定時器中斷

1、相關知識 振蕩周期1/12us; //振蕩周期又稱 S周期或時鐘周期&#xff08;晶振周期或外加振蕩周期&#xff09;。 狀態周期1/6us; 機器周期1us; 指令周期1~4us; ①51單片機有兩組定時器/計數器&#xff0c;因為既可以定時&#xff0c;又可以計數&#xff0c;故稱之為定時器…

【藍牙】win11 筆記本電腦連接 hc-06

文章目錄 前言步驟 前言 使用電腦通過藍牙添加串口 步驟 設置 -> 藍牙和其他設備 點擊 顯示更多設備 更多藍牙設置 COM 端口 -> 添加 有可能出現卡頓&#xff0c;等待一會 傳出 -> 瀏覽 點擊添加 hc-06&#xff0c;如果沒有則點擊 再次搜索 確定 添加成…

Android切換語言不退出App

1.需求 實現用戶選擇語言&#xff08;未點擊下一步&#xff09;&#xff0c;更新當前界面UI&#xff0c;點擊下一步后&#xff0c;更新App的語言&#xff0c;并進行保存。 實現目標&#xff1a; 1.設置App的語言&#xff0c;本地進行保存 2.updateResources更新本地語言配置…

一鍵獲取Linux主機配置信息shell腳本,含網卡詳情,網卡綁定等

cat > /tmp/get_os_info.sh <<"EOF"#!/bin/bashexport LANG=en_US.UTF-8# 如果 cat /proc/1/cgroup | grep docker | wc -l 大于0 或 systemd-detect-virt 返回 docker,則為 docker容器,# 如果 virt-what 返回 kvm或vmware或hyperv或xen、xen-hvm、lxc 或…

2 XDMA IP中斷

三種中斷 1. Legacy 定義&#xff1a;Legacy 中斷是傳統的中斷處理方式&#xff0c;使用物理中斷線&#xff08;例如 IRQ&#xff09;來傳遞中斷信號。缺點&#xff1a; 中斷線數量有限&#xff0c;通常為 16 條&#xff0c;限制了可連接設備的數量。中斷處理可能會導致中斷風…

【算法】時間復雜度以及O(N^2)的排序

目錄 1.常數時間的操作 2.時間復雜度 2.1.以選擇排序為例 2.2.O(n^2)從何而來 2.3.冒泡排序 2.3.1.抑或運算 2.4.插入排序 3.二分法 3.1.局部最小 4.遞歸 4.1.遞歸行為時間復雜度的估計 1.常數時間的操作 一個操作如果和樣本的數據量無關&#xff0c;每次都是固定時…

2021 年 3 月青少年軟編等考 C 語言五級真題解析

目錄 T1. 紅與黑思路分析T2. 密室逃脫思路分析T3. 求逆序對數思路分析T4. 最小新整數思路分析T1. 紅與黑 有一間長方形的房子,地上鋪了紅色、黑色兩種顏色的正方形瓷磚。你站在其中一塊黑色的瓷磚上,只能向相鄰的黑色瓷磚移動。請寫一個程序,計算你總共能夠到達多少塊黑色的…