轮廓检测

  • 一、轮廓检测
  • 二、轮廓的层级
  • 三、轮廓的特征
    • 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…