【opencv】第8章 图像轮廓与图像分割修复

news2025/1/13 12:20:03

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.coloradmin.cn/o/2275960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BGP 泄露

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 目录 1. BGP 是什么&#xff1f; 2. 什么是 BGP 泄露&#xff1f; 3. 今天发生了什么&#xff1f; 4. 正常和被劫持状态下的路由示意图 5. 受影响区域 6. 责任在谁&#xff1f; 7. 有办法避免这…

数据结构与算法之二叉树: LeetCode 572. 另一棵树的子树 (Ts版)

另一棵树的子树 https://leetcode.cn/problems/subtree-of-another-tree/description/ 描述 给你两棵二叉树 root 和 subRoot检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false二叉树 tree …

动植物基因表达调控

1&#xff0c; on and off状态 以及表达的量 2&#xff0c; 基因调控的生物学影响&#xff1f; 超过400多种细胞类型&#xff0c;数目上37万亿 不是所有的基因都表达 为什么多核真核细胞需要基因调控&#xff1f; 单个细胞往多个细胞逐渐进化的过程&#xff0c;形成复杂的…

FreePBX 17 on ubuntu24 with Asterisk 20

版本配置&#xff1a; FreePBX 17&#xff08;最新&#xff09; Asterisk 20&#xff08;最新Asterisk 22&#xff0c;但是FreePBX 17最新只支持Asterisk 21&#xff0c;但是21非LTS版本&#xff0c;所以选择Asterisk 20&#xff09; PHP 8.2 Maria DB (v10.11) Node J…

“AI智能服务平台系统,让生活更便捷、更智能

大家好&#xff0c;我是资深产品经理老王&#xff0c;今天咱们来聊聊一个让生活变得越来越方便的高科技产品——AI智能服务平台系统。这个系统可是现代服务业的一颗璀璨明珠&#xff0c;它究竟有哪些魅力呢&#xff1f;下面我就跟大家伙儿闲聊一下。 一、什么是AI智能服务平台系…

Qt监控系统远程网络登录/请求设备列表/服务器查看实时流/回放视频/验证码请求

一、前言说明 这几个功能是近期定制的功能&#xff0c;也非常具有代表性&#xff0c;核心就是之前登录和设备信息都是在本地&#xff0c;存放在数据库中&#xff0c;数据库可以是本地或者远程的&#xff0c;现在需要改成通过网络API请求的方式&#xff0c;现在很多的服务器很强…

【网络协议】动态路由协议

前言 本文将概述动态路由协议&#xff0c;定义其概念&#xff0c;并了解其与静态路由的区别。同时将讨论动态路由协议相较于静态路由的优势&#xff0c;学习动态路由协议的不同类别以及无类别&#xff08;classless&#xff09;和有类别&#xff08;classful&#xff09;的特性…

安装完docker后,如何拉取ubuntu镜像并创建容器?

1. 先docker拉取ubuntu镜像 docker search ubuntu #搜索ubuntu 镜像 docker pull ubuntu:22.04 #拉取ubuntu 镜像 docker images #下载完成后&#xff0c;查看已经下载的镜像 docker run --name ubuntu_container -dit ubuntu:22.04 /bin/bash # docker container -l 2.…

互联网全景消息(10)之Kafka深度剖析(中)

一、深入应用 1.1 SpringBoot集成Kafka 引入对应的依赖。 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupI…

React Fiber框架中的Render渲染阶段——workLoop(performUnitOfWork【beginWork与completeWork】)

触发渲染过程——renderRoot renderRoot 是一个函数&#xff0c;用于触发渲染工作。它通常会调用并递归地执行一系列的渲染任务&#xff0c;直到完成整个更新过程。这个过程包括执行 Fiber 树中的 beginWork 和 completeWork&#xff0c;以及渲染新状态或 DOM。 function ren…

STM32F1学习——ADC模数转换器

一、ADC模数转换器 ADC的全称 Analog-Digital Converter 模拟-数字转换器&#xff0c;他可以用来将引脚上连续变换的模拟电压转换为内存中存储的数字变量。 ADC有两个重要指标&#xff0c;分辨率和频率。 STM32的ADC是 12位 逐次逼近型&#xff0c;1us转换时间&#xff0c;也就…

[每周一更]-(第131期):Go并发协程总结篇

Go语言的并发是通过协程&#xff08;goroutine&#xff09;实现的。Go协程是轻量级的线程&#xff0c;允许多个任务同时执行&#xff0c;且Go运行时会高效地管理它们。在Go中使用并发协程的方式非常简便&#xff0c;也很强大。以下是一些关于Go协程的基础用法和并发控制方法&am…

Ecdsa密钥在线生成工具

具体前往&#xff1a;ECC公钥私钥对在线生成器

llama.cpp 模型可视化工具 GGUF Visualizer

llama.cpp 模型可视化工具 GGUF Visualizer 1. GGUF Visualizer for VS Code (gguf-viz)1.1. Features1.2. Extension Settings References GGUF Visualizer https://marketplace.visualstudio.com/items?itemNameAgainstEntropy.gguf-viz 1. GGUF Visualizer for VS Code (g…

【DAPM杂谈之三】DAPM的初始化流程

本文主要分析DAPM的设计与实现 内核的版本是&#xff1a;linux-5.15.164&#xff0c;下载链接&#xff1a;Linux内核下载 主要讲解有关于DAPM相关的知识&#xff0c;会给出一些例程并分析内核如何去实现的 /**************************************************************…

HarmonyOS:@LocalBuilder装饰器: 维持组件父子关系

一、前言 当开发者使用Builder做引用数据传递时&#xff0c;会考虑组件的父子关系&#xff0c;使用了bind(this)之后&#xff0c;组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题&#xff0c;引入LocalBuilder装饰器。…

Pytorch导出onnx模型并在C++环境中调用(含python和C++工程)

Pytorch导出onnx模型并在C环境中调用&#xff08;含python和C工程&#xff09; 工程下载链接&#xff1a;Pytorch导出onnx模型并在C环境中调用&#xff08;python和C工程&#xff09; 机器学习多层感知机MLP的Pytorch实现-以表格数据为例-含数据集和PyCharm工程中简单介绍了在…

git打补丁

1、应用场景 跨仓库升级 开发项目B使用的是开源项目A。开源项目A发现漏洞&#xff0c;作者进行了修复&#xff0c;我们可以通过使用git补丁的方式&#xff0c;将作者修改的内容复制到我 们的项目B中。 2、TortoiseGit方式 源仓库 格式化补丁 根据提交数量&#xff0c;生成…

计算机网络 (34)可靠传输的工作原理

前言 计算机网络可靠传输的工作原理主要依赖于一系列协议和机制&#xff0c;以确保数据在传输过程中能够准确无误地到达目的地。 一、基本概念 可靠传输指的是数据链路层的发送端发送什么&#xff0c;在接收端就收到什么&#xff0c;即保证数据的完整性、正确性和顺序性。由于网…

基于ADAS 与关键点特征金字塔网络融合的3D LiDAR目标检测原理与算法实现

一、概述 3D LiDAR目标检测是一种在三维空间中识别和定位感兴趣目标的技术。在自动驾驶系统和先进的空间分析中&#xff0c;目标检测方法的不断演进至关重要。3D LiDAR目标检测作为一种变革性的技术&#xff0c;在环境感知方面提供了前所未有的准确性和深度信息. 在这里&…