OpenCV——轮廓检测

发布于:2025-06-25 ⋅ 阅读:(24) ⋅ 点赞:(0)

一、轮廓检测

轮廓可以简单的描述为具有相同颜色或灰度的连续点连在一起的一条曲线,轮廓通畅会显示出图像中物体的形状。关于轮廓的检测,最容易想到的办法是跟踪检测的边缘点,从而找出闭合的轮廓线。但是如果把这个思路付诸行动就会发现情况并非想象的那么简单,因为边缘往往在梯度很弱的地方消失,而且轮廓线有时会有多个分支。实际上,边缘线中很少存在完美的轮廓线,普遍的情况是包含很多细小的、不连续的轮廓片段。

//在二值图像中寻找轮廓
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++) {
            //将轮廓数据转换为MatOfPoint2f
            contour.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++) {
            //将轮廓数据转换为MatOfPoint2f
            contour.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++) {
            //将轮廓数据转换为MatOfPoint2f
            contour.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;
    }
}

二值化图像:

在这里插入图片描述

绘有凸包的图像:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到