OpenCV——輪廓檢測

輪廓檢測

  • 一、輪廓檢測
  • 二、輪廓的層級
  • 三、輪廓的特征
    • 3.1、輪廓面積
    • 3.2、輪廓周長
    • 3.3、邊界矩形
    • 3.4、最小外接圓
    • 3.5、近似輪廓
    • 3.6、凸包

一、輪廓檢測

輪廓可以簡單的描述為具有相同顏色或灰度的連續點連在一起的一條曲線,輪廓通暢會顯示出圖像中物體的形狀。關于輪廓的檢測,最容易想到的辦法是跟蹤檢測的邊緣點,從而找出閉合的輪廓線。但是如果把這個思路付諸行動就會發現情況并非想象的那么簡單,因為邊緣往往在梯度很弱的地方消失,而且輪廓線有時會有多個分支。實際上,邊緣線中很少存在完美的輪廓線,普遍的情況是包含很多細小的、不連續的輪廓片段。

//在二值圖像中尋找輪廓
void Imgproc.findContours(Mat image, List<MatOfPoint> contours, Mat hierarchy, int mode, int method)
  • image:輸入圖像,必須是8位單通道二值圖或灰度圖。如果是灰度圖,像素值為0的仍視為0,而像素值不為0的視作1,如此灰度圖也可作為二值圖處理
  • contours:檢測到的輪廓
  • hierarchy:輪廓的層級,包含了對輪廓之間的拓撲關系的描述。Hierarchy中的元素數量和輪廓中的元素數量是一致的。第i個輪廓contours[i]有著相對應的4個hierarchy索引,分別是hierarchy[i][0]、hierarchy[i][1]、hierarchy[i][2]和hierarchy[i][3]。它們分別是輪廓的同層下一個輪廓索引、同層上一個輪廓索引、第1個子輪廓索引和父輪廓索引。如果第i個輪廓沒有下一個同層輪廓、子輪廓或父輪廓,則對應的索引用負數表示。
  • mode:輪廓提取模式,具體如下
    • Imgproc.RETR_EXTERNAL:只檢測最外層輪廓,所有輪廓的hierarchy[i][2]和hierarchy[i][3]均設為-1
    • Imgproc.RETR_LIST:檢測所有的輪廓,但輪廓之間不建立層級關系
    • Imgproc.RETR_CCOMP:檢測所有的輪廓,所有輪廓建立一個樹形層級結構
  • method:輪廓逼近方法,可選參數如下:
    • Imgproc.CHAIN_APPROX_NONE:存儲所有輪廓點,兩個相鄰的輪廓點(x1, y1)和(x2, y2)必須是8連通,即max(abs(x1-x2), abs(y2-y1))=1
    • Imgproc.CHAIN_APPROX_SIMPLE:壓縮水平方向、垂直方向和對角線方向的線段,只保存線段的端點
    • Imgproc.CHAIN_APPROX_TC89_L1:使用teh-Chin1 chain近似算法中的L1算法
    • Imgproc.CHAIN_APPROX_TC89_KCOS:使用teh-Chin1 chain近似算法中的KCOS算法

二、輪廓的層級

在 findContours()函數中有一個重要的參數 hierarchy,即輪廓的層級,它包含了對輪廓之間拓撲關系的描述。輪廓的層級是針對每個輪廓的,如果一張圖像有100個輪廓,則hierarchy 就有 100 項,其中第i項為 hierarchy[i]。如果輪廓 A 被另外一個輪 B 包圍,則AB 之間就是父子關系,其中 B輪廓為父輪廓,A 輪廓為子輪廓。父輪廓的層級比子輪廓高級,子輪廓還可以有子輪廓。下面用一個實例說明輪的層級關系。

如圖所示,這個嵌套的圖形中有6個輪廓,分別標記為 1~6號,它們之間有層級關系,其中1號輪廓在最外面,它的層級比其他的輪廓都要高。1號輪有3個子輪,分別為 2、3 和 5號輪廓,其中3號輪廓又有4號子輪廓,5 號輪廓又有6號子輪廓。

有6個輪廓的圖像:
在這里插入圖片描述

輪廓的層級結構:
在這里插入圖片描述

為了標識輪廓之間的關系,每個輪廓的Hierarchy[i]都是包含4個值的數組,這4個數組值標記為hierarchy[i][0]~hierarchy[i][3],它們的含義一次為Next、Previous、First_child和Parent:

  • Next:表示同一層次的下一個輪廓的編號,如2號輪廓的Next是3號輪廓
  • Previous:表示同一層次上的前一個輪廓編號,如3號輪廓的Previous是2號輪廓
  • First_Child:表示第1個子輪廓的編號,如1號輪廓的First_Child是2號輪廓
  • Parent:表示父輪廓編號,如5號輪廓的Parent是1號輪廓

某些輪廓是沒有子輪廓、父輪廓或同一層次的下一個輪廓的,這時用-1 表示。現在可以用這個數組來描述輪廓的結構了。例如,2號輪廓可以表示為[3,-1,-1,1],因為它只有 Next (3 號輪廓,用3表示)和 Parent(1號輪廓,用1表示),沒有 Previous 和 First Child(用-1 表示)。同理,5 號輪廓可以表示為[-1,3,6,1]。

public class ContourHierarchy {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/contour.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖像檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//畫出輪廓圖并顯示Mat imgCanny = new Mat(src.height(), src.width(), CvType.CV_8UC3, new Scalar(255, 255, 255));for (int i = 0; i < contour.size(); i++) {Imgproc.drawContours(imgCanny, contour, i, new Scalar(0, 0, 0), 1);}HighGui.imshow("Contours", imgCanny);HighGui.waitKey(0);//控制臺輸出hierarchy層級數據for (int i = 0; i < contour.size(); i++) {double[] d = hierarchy.get(0, i);for (int j = 0; j < d.length; j++) {System.out.print(d[j] + ",");}System.out.println();}System.exit(0);}
}

樹形結構:第一行為0號輪廓是1號輪廓的父級,共7個輪廓。

-1.0,-1.0,1.0,-1.0,
-1.0,-1.0,2.0,0.0,
3.0,-1.0,-1.0,1.0,
5.0,2.0,4.0,1.0,
-1.0,-1.0,-1.0,3.0,
-1.0,3.0,6.0,1.0,
-1.0,-1.0,-1.0,5.0,

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

輪廓圖:
在這里插入圖片描述

在檢測出輪廓之后,還需要drawContours()函數將輪廓繪制出來:

//繪制輪廓或輪廓內部
void Imgproc.drawContours(Mat image, List<MatOfPoint> contours, int contourIdx, Scalar color, int thickness)
  • image:用于繪制輪廓的圖像
  • contours:輸入的輪廓數據
  • contourIdx:要繪制的輪廓的索引,如為負數,則繪制所有輪廓
  • color:繪制輪廓的顏色
  • thickness:繪制輪廓的線條粗細。如為負數,則繪制輪廓內部;如為1,則繪制該輪廓及嵌套的輪廓;如為2,則繪制該輪廓、嵌套的輪廓及嵌套輪廓的輪廓;其余以此類推。此參數僅當輪廓數據中含有層級數據時有效
public class FindContours {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/butterfly.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contours = new ArrayList<>();Imgproc.findContours(binary, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);//畫出輪廓圖并顯示Mat imgBinary = new Mat(src.height(), src.width(), CvType.CV_8UC3, new Scalar(255, 255, 255));for (int i = 0; i < contours.size(); i++) {Imgproc.drawContours(imgBinary, contours, i, new Scalar(0, 0, 0), 1);}HighGui.imshow("Contours from Binary", imgBinary);HighGui.waitKey(0);//進行Canny邊緣檢測并顯示Mat canny = new Mat();Imgproc.Canny(src, canny, 60, 200);HighGui.imshow("Canny", canny);HighGui.waitKey(0);//根據Canny結果檢測輪廓List<MatOfPoint> contour2 = new ArrayList<>();Imgproc.findContours(canny, contour2, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);//畫出輪廓并顯示Mat imgCanny = new Mat(src.height(), src.width(), CvType.CV_8UC3, new Scalar(255, 255, 255));for (int i = 0; i < contour2.size(); i++) {Imgproc.drawContours(imgCanny, contour2, i, new Scalar(0, 0, 0), 1);}HighGui.imshow("Contours from Canny", imgCanny);HighGui.waitKey(0);System.exit(0);}
}

由于Canny邊緣檢測的輸出質量較高,其相應的輪廓圖也更清晰一些,二值圖生成的輪廓則有一些干擾。

二值圖:
在這里插入圖片描述

二值圖的輪廓:
在這里插入圖片描述

Canny邊緣圖:
在這里插入圖片描述

Canny邊緣圖繪制的輪廓圖:
在這里插入圖片描述

三、輪廓的特征

獲取輪廓后可以利用輪廓的各種特征來區分和識別物體。輪廓的特征包括面積、周長、邊界矩形、近似輪廓的凸包等。

3.1、輪廓面積

//計算輪廓面積
double Imgproc.contourArea(Mat contour, boolean oriented)
  • contour:輪廓頂點數據
  • oriented:面積是否具有方向性的標志。如為true,則函數返回有符號的面積值。面積是否帶符號取決與方向為順時針還是逆時針。默認情況下,此標志為false,函數返回面積的絕對值。
public class ContourArea {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/contour.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//輸出各輪廓面積for (int i = 0; i < contour.size(); i++) {double d = Imgproc.contourArea(contour.get(i));System.out.println(i + ":" + d);}System.exit(0);}
}

二值圖:
在這里插入圖片描述

面積:

0:121801.0
1:56306.0
2:1998.0
3:1979.0
4:306.0
5:1979.0
6:306.0

3.2、輪廓周長

//計算輪廓周長或曲線長度
double Imgproc.arcLength(MatOfPoint2f curve, boolean closed)
  • curve:輪廓或曲線的數據
  • closed:曲線是否閉合的標志
public class ArcLength {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/contour.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//計算各個輪廓的周長并在控制臺輸出MatOfPoint2f dst = new MatOfPoint2f();for (int i = 0; i < contour.size(); i++) {contour.get(i).convertTo(dst, CvType.CV_32F);double d = Imgproc.arcLength(dst, true);System.out.println(i + ":" + Math.round(d));}System.exit(0);}
}

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

周長:

0:1396
1:887
2:181
3:173
4:66
5:173
6:66

3.3、邊界矩形

邊界矩形有直邊界矩形(沒有旋轉的矩形)和最小外接矩形(旋轉的邊界矩形)兩種,OpenCV中分別用 boundingRect()函數和 minAreaRect()函數獲取這兩種邊界矩形。用 boundingRect()函數找到的邊界矩形是不經過旋轉的,因此不是面積最小的矩形,用minAreaRect()函數找到的邊界矩形才是面積最小的。直邊界矩形和最小外接矩形的區別如圖 9-17 所示。圖中傾斜的矩形是最小外接矩形,沒有傾斜的矩形是直邊界矩形。很明顯,最小外接矩形的面積比直邊界矩形要小得多。

//計算一個二維點集或灰度圖中非零像素的邊界矩形
Rect Imgproc.boundingRect(Mat array)
  • array:輸入的二維點集或灰度圖
public class RoundingRect {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/contour.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//在圖像上繪制各輪廓的邊界圖像src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/contour.png", Imgcodecs.IMREAD_GRAYSCALE);for (int i = 0; i < contour.size(); i++ ) {Rect rect = Imgproc.boundingRect(contour.get(i));Imgproc.rectangle(src, new Point(rect.x, rect.y), new Point(rect.x+rect.width, rect.y+rect.height), new Scalar(0, 0, 255), 3);}//在屏幕顯示HighGui.imshow("Rect", src);HighGui.waitKey(0);System.exit(0);}
}

二值圖:

在這里插入圖片描述

直邊界矩形的圖像;

在這里插入圖片描述

OpenCV中獲取最小外接矩形的函數圓形如下;

//尋找輸入二維點集的最小外接矩形
RotatedRect Imgproc.minAreaRect(MatOfPoint2f points)
  • points:輸入的二維點集

該函數的返回值類型是旋轉矩形,即RotatedRect類,該類常用的成員變量有center、width、height和angle,如下圖:

在這里插入圖片描述

但是根據這些數值把這個旋轉矩形畫出來比較繁瑣,為此需要用boxPoints()函數獲取旋轉矩形的4個頂點:

//獲取旋轉矩形的4個頂點
void Imgproc.boxPoints(RotatedRect box, Mat points)
  • box:輸入的旋轉矩形
  • points:輸出的4個頂點
public class MinAreaRect {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//重新后區彩色圖像,用于繪制最小外接矩形src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);//參數準備MatOfPoint2f dst = new MatOfPoint2f();Mat pts = new Mat();Scalar red = new Scalar(0, 0, 255);float[] data = new float[8];//用于獲取點集數據for (int n = 0; n < contour.size(); n++) {//將輪廓數據轉換為MatOfPoint2fcontour.get(n).convertTo(dst, CvType.CV_32F);//獲取最小外接矩形(旋轉矩形)RotatedRect rect = Imgproc.minAreaRect(dst);//獲取旋轉矩形的4個頂點Imgproc.boxPoints(rect, pts);pts.get(0, 0, data);//將4個頂點轉換為Point類Point pt1 = new Point(data[0], data[1]);Point pt2 = new Point(data[2], data[3]);Point pt3 = new Point(data[4], data[5]);Point pt4 = new Point(data[6], data[7]);//繪制最小外接矩形的4條邊Imgproc.line(src, pt1, pt2, red, 2);Imgproc.line(src, pt2, pt3, red, 2);Imgproc.line(src, pt3, pt4, red, 2);Imgproc.line(src, pt4, pt1, red, 2);}//顯示HighGui.imshow("MinAreaRect", src);HighGui.waitKey(0);System.exit(0);}
}

二值化圖像:

繪有最小外接矩形的圖像:

在這里插入圖片描述

3.4、最小外接圓

有些情況下需要獲得一個對象的最小外接圓,這是需要用到minEncloingCircle()函數:

//尋找包圍點集的最小面積的圓
void Imgproc.minEnclosingCircle(MatOfPoint2f points, Point center, float[] radius)
  • points:輸入的二維點集
  • center:輸出圓的圓心
  • radius:輸出圓的半徑
public class MinCircle {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//重新后區彩色圖像,用于繪制最小外接矩形src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);//參數準備Point center = new Point();float[] radius = new float[3];Scalar red = new Scalar(0, 0, 255);MatOfPoint2f dst = new MatOfPoint2f();for (int i = 0; i < contour.size(); i++) {//將輪廓數據轉換為MatOfPoint2fcontour.get(i).convertTo(dst, CvType.CV_32F);//獲取最小外接圓Imgproc.minEnclosingCircle(dst, center, radius);//繪制最小外接圓int r = Math.round(radius[0]);Imgproc.circle(src, center, r, red, 2);}//顯示HighGui.imshow("MinCircle", src);HighGui.waitKey(0);System.exit(0);}
}

二值圖:
在這里插入圖片描述

繪有最下外接圓的圖像:
在這里插入圖片描述

3.5、近似輪廓

有時用矩陣或圓形逼近物體輪廓差異會較大,此時可用多邊形逼近輪廓。OpenCV中獲取逼近輪廓的多邊形的函數:

//尋找逼近輪廓的多邊形(曲線)
void Imgproc.approxPolyDP(MatOfPoint2f curve, MatOfPoint2f approxCurve, double epsilon, boolean closed)
  • curve:輸入的二維點集
  • approxCurve:逼近的結果,類型應與輸入匹配
  • epsilon:逼近的精度,即原始曲線和逼近多邊形(曲線)的最大距離
  • closed:如為true,則表示逼近曲線為閉合曲線(最后一個頂點和第1個頂點相連),否則為不閉合
public class ApproxPolyDP {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 90, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Mat hierarchy = new Mat();Imgproc.findContours(binary, contour, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//重新后區彩色圖像,用于繪制最小外接矩形src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/seed.png", Imgcodecs.IMREAD_GRAYSCALE);//參數準備Scalar red = new Scalar(0, 0, 255);MatOfPoint2f mop = new MatOfPoint2f();MatOfPoint2f dst = new MatOfPoint2f();//繪制逼近輪廓的多邊形for (int i = 0; i < contour.size(); i++) {//將輪廓數據轉換為MatOfPoint2fcontour.get(i).convertTo(mop, CvType.CV_32F);//輪廓面積太小的跳過不畫double area = Imgproc.contourArea(contour.get(i));if (area < 100) {continue;}//獲取逼近輪廓的多邊形Imgproc.approxPolyDP(mop, dst, 4, true);//將多邊形的數據轉換成數組以便于畫圖int row = dst.rows();float[] data = new float[row * 2];dst.get(0, 0, data);//畫多邊形的邊for (int j = 0; j < row - 1; j++) {Point pt1 = new Point(data[j * 2], data[j * 2 + 1]);Point pt2 = new Point(data[j * 2 + 2], data[j * 2 + 3]);Imgproc.line(src, pt1, pt2, red, 2);}//連接多邊形第1個和最后1個頂點Imgproc.line(src, new Point(data[0], data[1]), new Point(data[row * 2 - 2], data[row * 2 - 1]), red, 2);}//顯示HighGui.imshow("ApproxPolyDP", src);HighGui.waitKey(0);System.exit(0);}
}

二值圖:

在這里插入圖片描述

繪有近似輪廓的圖像:
在這里插入圖片描述

3.6、凸包

有些物體的形狀用多邊形逼近仍然不理想,如人手,此時可以利用凸包來近似。所謂凸包是指將最外圍的點連接起來構成凸邊形,如下圖:

在這里插入圖片描述

//尋找點集的凸包
void Imgproc.convexHull(MatOfPoint points, MatOfInt hull, boolean clockwise)
  • points:輸入的二維點集
  • hull:輸出的凸包頂點
  • clockwise:方向標志,如為true,則表示凸包為順時針方向,否則為逆時針方向。此處假設坐標系系統的x軸指向右方,y軸指向上方
public class ConvexHull {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并轉換為二值圖像 并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/palm.png", Imgcodecs.IMREAD_GRAYSCALE);Mat binary = new Mat();Imgproc.threshold(src, binary, 230, 255, Imgproc.THRESH_BINARY);HighGui.imshow("binary", binary);HighGui.waitKey(0);//根據二值圖檢測輪廓List<MatOfPoint> contour = new ArrayList<>();Imgproc.findContours(binary, contour, new Mat(), Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);//重新后區彩色圖像,用于繪制最小外接矩形src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo2/palm.png");//參數準備MatOfInt onehull = new MatOfInt();List<MatOfPoint> hulls = new ArrayList<>();MatOfPoint c = new MatOfPoint();//繪制凸包for (int i = 0; i < contour.size(); i++) {//輪廓面積太小的跳過不畫c = contour.get(i);//第i個輪廓double area = Imgproc.contourArea(c);if (area < 100) {continue;}//獲取凸包,并將索引值轉換為點的坐標onehull為索引值Imgproc.convexHull(c, onehull);hulls.add(indexToPoint(c, onehull));}//繪制凸包for (int i = 0; i < hulls.size(); i++) {Imgproc.drawContours(src, hulls, i, new Scalar(0, 0, 255), 2);}//顯示HighGui.imshow("ConvexHull", src);HighGui.waitKey(0);System.exit(0);}//將輪廓的索引值轉換為點坐標的子程序public static MatOfPoint indexToPoint(MatOfPoint contour, MatOfInt index) {//將兩個參數轉換為數組類型int[] ind = index.toArray();Point[] con = contour.toArray();//獲取點的坐標Point[] pts = new Point[ind.length];for (int i = 0; i < ind.length; i++) {pts[i] = con[ind[i]];}//將點的坐標轉換成MatOfPoint數據類型MatOfPoint hull = new MatOfPoint();hull.fromArray(pts);return hull;}
}

二值化圖像:

在這里插入圖片描述

繪有凸包的圖像:

在這里插入圖片描述

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

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

相關文章

高等概率論題解-心得筆記【15】

文章目錄 拓撲參考文獻 拓撲 參考文獻 《測度論基礎與高等概率論》

Windows 10關閉自動更新功能

Windows 10關閉自動更新功能&#xff0c;大家是不是經常用下面的幾個步驟&#xff1a; 1、禁用Windows Update服務&#xff1b; 2、在組策略里關閉Win10自動更新相關服務&#xff1b; 3、禁用任務計劃里邊的Win10自動更新&#xff1b; 4、在注冊表中關閉Win10自動更新&…

[Meetily后端框架] 配置指南 | 后端API網關 | API文檔體系

鏈接: https://github.com/Zackriya-Solutions/meeting-minutes docs&#xff1a;會議紀要管理系統 本項目是一個專門用于**處理會議記錄**的后端系統。 系統接收會議文本內容&#xff0c;利用先進的AI模型自動識別關鍵信息&#xff0c;包括行動項、決策內容以及截止期限。 處…

Flink2.0 配置 historyserver

Flink2.0 配置 historyserver 主要是去修改config.yaml配置文件 主要修改的點有兩個 網上很多文檔都是寫的只配置一個 都是坑啊 historyserver :歷史服務器 運行 Flink job 的集群一旦停止(例如yarn模式&#xff0c;程序一旦停止&#xff0c;集群也就關閉了)&#xff0c;只能去…

LLM的訓練過程

一般而言&#xff0c;訓練一個完整的 LLM 需要經過圖1中的三個階段——Pretrain、SFT 和 RLHF。 1.預訓練 Pretrain&#xff0c;即預訓練&#xff0c;是訓練 LLM 最核心也是工程量最大的第一步。LLM 的預訓練和傳統預訓練模型非常類似&#xff0c;同樣是使用海量無監督文本對隨…

用 AI + Canvas 生成圖形、動畫與圖表

摘要 隨著人工智能&#xff08;AI&#xff09;技術與 Web 可視化的結合&#xff0c;前端開發者可以通過自然語言生成復雜的圖表、動畫和交互式畫布&#xff0c;極大地提升了開發效率和用戶體驗。本文作為《AI 前端&#xff1a;構建智能化 Web 應用的未來》專欄的第七篇&#…

SQL Server for Linux 如何實現高可用架構

關鍵詞&#xff1a;SQL Server for Linux、高可用、讀寫分離、動態擴容、Always On、可用性組 &#x1f4cb; 文章目錄 前言&#xff1a;Linux上的SQL Server不再是夢高可用架構設計 Always On 可用性組故障轉移集群實例 讀寫分離架構 可用性組讀寫分離應用層讀寫分離 動態擴…

【51單片機流水燈控制4種造型,按下1,2,3,4時,數碼管對應顯示鍵號,同時流水燈對應四種造型】2022-6-1

緣由流水燈控制4種造型&#xff0c;按下1,2,3,4時&#xff0c;數碼管對應顯示鍵號&#xff0c;同時流水燈對應四種造型-編程語言-CSDN問答 #include "REG52.h" unsigned char code smgduan[]{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5…

設計模式 - 工廠方法

工廠方法是一種設計模式&#xff0c;對工廠制造方法進行接口規范化&#xff0c;允許子類工廠決定具體知道哪類產品的實例&#xff0c;最終降低系統耦合&#xff0c;使系統的可維護性、可擴展性等得到提升。 一、工廠的多元化與專業化 要實例化對象&#xff0c;就得用到關鍵詞“…

數據應該如何組織,才能讓Excel“讀懂”?

前言&#xff1a;如果你希望Excel能“讀懂”你的數據&#xff0c;就得學會讓排序、篩選、數據透視表、函數等這些功能為我們服務。 假設你在和一個非常聰明但有點“死板”的機器人&#xff08;Excel&#xff09;對話&#xff0c;你必須用它能理解的語言來組織信息。 “一維表”…

js防止重復提交的3種解決方案

防止 javascript 重復點擊和提交的關鍵方法有三種&#xff1a;1. 禁用按鈕法&#xff0c;點擊后立即禁用按鈕并更改文本提示&#xff0c;請求完成后恢復&#xff1b;2. 節流函數&#xff08;throttle&#xff09;&#xff0c;限制函數在設定時間間隔內僅執行一次&#xff0c;適…

【信創-k8s】銀河麒麟V10國防版+鯤鵬/飛騰(arm64架構)在線/離線部署k8s1.30+kubesphere

銀河麒麟作為國家核高基專項的重要成果&#xff0c;國防版憑借其卓越的安全性和可靠性&#xff0c;已成為軍工領域的首選操作系統。之前我們在適配麒麟V4國防版的過程中已發現諸多安全性要求&#xff0c;而麒麟V10國防版在安全防護等級上又達到了更高的級別。 本文將主要演示離…

解鎖單周期MIPS硬布線:Logisim實戰全攻略

目錄 一、引言二、MIPS 架構與單周期設計原理2.1 MIPS 架構概述2.2 單周期設計原理剖析 三、Logisim 工具基礎3.1 Logisim 簡介3.2 基本操作與組件認識 四、單周期 MIPS 硬布線設計步驟4.1 了解 MIPS 指令集4.2 搭建數據通路4.3 設計硬布線控制器4.4 在 Logisim 中創建電路 五、…

7.4.2B+樹

B樹&#xff1a; (1)每個分支節點最多有m個子樹(孩子節點)。 階&#xff1a;即看當前的B樹是幾階B樹&#xff0c;就看每個分支節點最多有幾個子樹&#xff0c;還是看最下一層有幾個分叉就是幾階&#xff1f;&#xff1f;&#xff1f; 葉子節點&#xff1a;最下邊的一層叫葉子…

MFC獲取本機所有IP、局域網所有IP、本機和局域網可連接IP

獲取本機所有IP地址 // 獲取本機所有IP地址 int CMachine::GetLocalIPs(std::vector<CString>& vIPValue) {//返回IP數量&#xff0c; -1表示獲取失敗vIPValue.clear();int IpNum 0;//1.初始化wsa WSADATA wsaData;int ret WSAStartup(MAKEWORD(2, 2), &wsaD…

【C語言】貪吃蛇小游戲

1. 所需知識 C語言函數、枚舉、結構體、動態內存管理、預處理指令、鏈表、Win32 API... 2. Win32 API介紹 2.1 Win32 API windows這個多作業系統除了協調應用程序的執行、分配內存、管理資源之外&#xff0c;它同時也是一個很大的服務中心&#xff0c;調用這個服務中心的各種…

PostgreSQL 容器化分布式技術方案

&#x1f4cb; 目錄 引言&#xff1a;為什么選擇容器化PostgreSQLPostgreSQL容器化基礎分布式架構設計高可用實現方案讀寫分離架構動態擴縮容策略生產環境實踐總結與展望 引言&#xff1a;為什么選擇容器化PostgreSQL 在數字化轉型的浪潮中&#xff0c;數據庫作為企業的"…

NV025NV033美光固態閃存NV038NV040

美光固態閃存技術突破與市場布局深度解析 一、技術突破&#xff1a;232層NAND閃存與高密度存儲的革新 美光NV系列固態閃存的核心競爭力源于其232層NAND閃存技術&#xff0c;這一技術通過垂直堆疊工藝&#xff0c;將存儲單元層層疊加&#xff0c;宛如在指甲蓋面積內構建超過20…

Matplotlib 繪圖庫從入門到精通:Python 數據可視化全解析

引言 在數據科學的世界里&#xff0c;"一圖勝千言" 這句話有著深刻的含義。數據可視化不僅是數據分析師展示成果的重要手段&#xff0c;更是數據科學家探索數據、發現規律的強大工具。Matplotlib 作為 Python 生態系統中最著名的數據可視化庫&#xff0c;為我們提供…

北斗導航 | 基于CNN-LSTM-PSO算法的接收機自主完好性監測算法

接收機自主完好性監測 原理概述1. 算法架構2. 核心創新點3. 工作流程數學模型1. CNN特征提取2. LSTM時序建模3. PSO優化決策MATLAB完整代碼算法優勢性能對比應用場景擴展方向原理概述 1. 算法架構 #mermaid-svg-fITV6QrXL1fNYFwG {font-family:"trebuchet ms",verda…