OpenCV基础(5)--图像分割与形态学操作

接着前面几篇CSDN的博客,继续写一点笔记。

1. 图像分割

将目标区域从背景中分离,常见方法:阈值分割(全局、局部阈值、OTSU)、区域分割(分水岭算法、GrabCut)、边缘分割(Sobel、Canny)、聚类(Kmeans)等;

本讲先讲前2类。

1.1 灰度阈值算法

1.1.1 全局阈值

解释:设置一个全局阈值,例如t, 小于t的全部设为0,大于t的全部设为255,呈现2级分化,提高对比度,形成二值化图像。

适用:明显呈现前后背景差异的图像。

方法:OpenCV提供的接口为threshold()。

threshold(src, dst, thresh, maxval, type);

src:只能单通道!这里用灰度图就行!
thresh: 设置的阈值
maxval:超过阈值需要取的最大值
type: THRESH_BINARY表示超过阈值取maxval,否则取0

示例:使用该种全局阈值方法处理图像,并根据本博客系列第(3)讲制作滑动条交互?

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
Mat src, dst, out;
int threshold_value = 100;
int threshold_max = 255;

// 定义回调函数
void threshold_back(int, void*)
{
    cvtColor(src, dst, COLOR_BGR2GRAY);
    threshold(dst, out, threshold_value, threshold_max, THRESH_BINARY);
    imshow("threshold image", out);
}

int main()
{
    src = imread("D:/Project_Images/cat2.jpeg");
    if (src.empty())
    {
        cout << " no image exist!" << endl;
        return -1;
    }
    imshow("origin image", src);
    namedWindow("threshold bar", WINDOW_AUTOSIZE);
    createTrackbar("阈值调节", "threshold bar", &threshold_value, threshold_max, threshold_back);
    // 初始调用显示一次
    threshold_back(0, 0);
    waitKey(0);
    return 0;
}

效果:

进阶:手动实现threshold(dst, out, threshold_value, threshold_max, THRESH_BINARY)?

提示:这个比较简单,遍历比较元素值即可。

1.1.2 自适应阈值算法

解释:局部做阈值化处理

适用:图像特征比较丰富,细节较多,光照不均匀,OCR识别等图片和场景。

方法:OpenCV接口为adaptiveThreshold()

void adaptiveThreshold( InputArray src, OutputArray dst,
                                     double maxValue, int adaptiveMethod,
                                     int thresholdType, int blockSize, double C );

解释关键几个参数:
adaptiveMethod: 只有2种, ADAPTIVE_THRESH_GAUSSIAN_C:高斯均值处理
                    ADAPTIVE_THRESH_MEAN_C:邻域均值处理;
thresholdType: 只有2种,THRESH_BINARY:二进制阈值,非0即1(255),非白(255)即黑(0)
                THRESHOLD_BINARY_INV
blockSize: 取像素的邻域块
C:偏移值

示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main() {
    // 读取灰度图像
    cv::Mat image = cv::imread("D:/Project_Images/coin.jpg", cv::IMREAD_GRAYSCALE);

    // 应用自适应阈值算法
    cv::Mat thresh;
    cv::adaptiveThreshold(image, thresh, 255, \
        cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 21, 10);

    // 显示原始图像和二值化结果
    cv::imshow("Original Image", image);
    cv::imshow("Adaptive Thresholding", thresh);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

效果:

1.2 OTSU大津法

解释:属于自适应阈值算法,也叫最大类间方差算法。

使用:双峰,前后背景差异明显(文档白底黑字),简单图像等。

方法:OpenCV接口仍为threshold()。

示例:

threshold(img, dst, 20, 255, THRESH_BINARY | THRESH_OTSU);

使用OTSU分割图像?

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main()
{
    Mat img = imread("D:/Project_Images/car.jpeg", IMREAD_GRAYSCALE);
    Mat dst;
    // 返回最佳阈值
    double threshold_value = threshold(img, dst, 20, 255, \
                                 THRESH_BINARY | THRESH_OTSU);    // 添加OTSU
    imshow("origin image", img);
    imshow("OTSU threshold image", dst);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

效果:

可以看到,效果明显。

进阶:尝试手动实现大津法代码?

提示:掌握最大类间方差variance的求法后则很快可以写出。

1.3 Grab Cut 图像分割

解释:简称抠图。实现前后背景分离,基于被分割的对象的指定边界框开始,使用高斯混合模型估计被分割的对象和背景的颜色分布。用户来确定前景和背景输入,完成图像切割。

适用:抠图。

方法:OpenCV提供方法grabCut()。

示例:对图像人物抠图

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

bool drawing = false;
cv::Point pt1, pt2;

void onMouse(int event, int x, int y, int flags, void* param) {

    cv::Mat* image = (cv::Mat*)param;

    // 显示原始图像和绘制轨迹
    cv::Mat tmpImage = image->clone();
    if (drawing) {
        cv::rectangle(tmpImage, pt1, cv::Point(x, y), cv::Scalar(0, 255, 0), 2);
    }
    cv::imshow("Input", tmpImage);

    if (event == cv::EVENT_LBUTTONDOWN) {
        drawing = true;
        pt1 = cv::Point(x, y);
    }
    else if (event == cv::EVENT_LBUTTONUP) {
        drawing = false;
        pt2 = cv::Point(x, y);

        // 进行GrabCut算法进行图像分割
        cv::Mat image = *(cv::Mat*)param;
        cv::Rect rectangle(pt1, pt2);
        cv::Mat result;
        cv::Mat bgModel, fgModel;
        cv::grabCut(image, result, rectangle, bgModel, fgModel, \
        5, cv::GC_INIT_WITH_RECT);

        // 将分割结果转换为前景掩码
        cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
        cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(0, 0, 0));
        image.copyTo(foreground, result);

        // 显示抠图结果
        cv::imshow("Foreground", foreground);
    }
}

int main() {
    cv::Mat image = cv::imread("D:/Project_Images/ikun.png");
    cv::namedWindow("Input");
    cv::imshow("Input", image);

    cv::setMouseCallback("Input", onMouse, &image);

    cv::waitKey(0);
    return 0;
}

结果:

可见,ikun被完整地扣了出来。

使用这种方法,无需二值化,抠图可以保留彩色。

2. 形态学操作

基本运算包括二值膨胀和腐蚀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、顶帽变换、颗粒分析、流域变换、灰度腐蚀和膨胀、灰度开闭运算、灰度形态学梯度。

主要针对二值图像(黑白)和灰度图像,以下讲解皆基于灰度图。

2.1 膨胀与腐蚀

膨胀:白色面积变大,黑色面积减小;

腐蚀:白色面积变小,黑色面积变大。

方法:使用OpenCV调用接口前,需要使用getStructuringElement()函数返回指定形状和尺寸的结构元素,用于传递内核的形状和大小,之后调用膨胀函数dilate();腐蚀函数erode()。

Mat getStructuringElement(int shape, Size esize, Point anchor)
用于传递内核形状和大小
shape:    MORPH_RECT,矩形;MORPH_CROSS,交叉形; MORPH_ELLISPSE, 椭圆形
esize:    内核尺寸
anchor:   锚点,默认Point(-1, -1), 表示中心点。

----------膨胀与腐蚀-----------

void dilate(Mat &src, Mat &dst, Mat &element)    // 膨胀

element: 膨胀操作的核,使用element = getStructingElement()获取

void erode(Mat &src, Mat &dst, Mat &element)    // 腐蚀

示例:使用滑动条更改图像,使其膨胀或腐蚀?

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

Mat src, dst;
int element_size = 3;
int max_size = 21;

int main()
{
    void onTrackBar(int, void*);
    src = imread("D:/Project_Images/shou.jpg");
    if (src.empty())
    {
        cout << "no image!" << endl;
        return -1;
    }
    imshow("origin image", src);
    namedWindow("腐蚀");
    createTrackbar("slide bar", "腐蚀", &element_size, max_size , onTrackBar);
    onTrackBar(element_size, 0);
    waitKey(0);
    return 0;
}

void onTrackBar(int, void*)
{
    int s = element_size * 2 + 1; // 实际是将腐蚀强度数值转化为核的边长数值
    Mat structure_element = getStructuringElement(MORPH_RECT, Size(s, s));
    // 膨胀只需改动API为dilate
    erode(src, dst, structure_element);
    imshow("腐蚀", dst);
}

效果:

可见,腐蚀会减少白色部分,增大黑色区域。

膨胀操作只需更换函数为dilate()即可。

2.2 开运算与闭运算

开运算:先腐蚀后膨胀(可用于消除图片上的孤立小白点);

闭运算:先膨胀后腐蚀(可用于消除图片上的孤立小黑点)。

实现方法:OpenCV接口为morphologyEx(img, out, MORPH_OPEN / MORPH_CLOSE,element)

示例:开运算消除白点?

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
    Mat img = imread("D:/Project_Images/pencil.jpeg");
    imshow("origin image", img);
    Mat out;
    Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
    morphologyEx(img, out, MORPH_OPEN, element);
    imshow("开运算", out);
    waitKey(0);
}

效果:

类似地,闭运算消除小黑点?(更改函数参数即可)

效果:

2.3 形态学梯度

解释:形态学梯度 = 膨胀图 - 腐蚀图

对二值图像(黑白图像)进行形态学梯度运算,可以突出物体边缘。通常采用形态学梯度来保留物体的边缘轮廓。

方法:morphologyEx(src, dst, CV_MOP_GRADIENT, kernel)

示例:使用滑块条调节形态学梯度操作的核?

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat img, out;
int val = 5;
void onBack(int, void *)
{
    Mat element = getStructuringElement(MORPH_RECT, Size(val,val));
    morphologyEx(img, out, MORPH_GRADIENT, element);
    imshow("形态学梯度", out);
}
int main()
{
    img = imread("D:/Project_Images/hand.jpeg");
    imshow("原图像", img);
    namedWindow("形态学梯度");
    createTrackbar("调节块", "形态学梯度", &val, 30, onBack);
    onBack(0, 0);
    waitKey(0);
}

效果:

2.4 顶帽操作

顶帽tophat = 原图像 - 开运算图像;

开运算去除白点(高亮),实际是局部降低了亮度区域。因此从原图像中减去开运算操作图可以得到的效果图能够突出原图像轮廓周围区域明亮的地方(其实就是: 开运算去除的哪些白点区域)。

方法:morphologyEx(src, dst, MORPH_TOPHAT,element)

示例:

int main()
{
    Mat src, dst;
    src = imread("D:/Project_Images/pencil.jpeg");
    if (src.empty())
    {
        cout << "no image" << endl;
        return -1;
    }
    imshow("原图像", src);
    Mat element = getStructuringElement(MORPH_RECT, Size(11, 11));
    morphologyEx(src, dst, MORPH_TOPHAT, element);
    imshow("顶帽处理", dst);
    waitKey(0);
    return 0;
}

2.5 黑帽操作

黑帽 = 闭运算 - 原图像

主要是为了突出比原图像轮廓周围区域更暗的区域。(突出原图像的黑色边缘)

方法:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

Mat src, dst;
int val = 3;
void onBack(int , void *)
{
    Mat element = getStructuringElement(MORPH_RECT, Size(val, val));
    morphologyEx(src, dst, MORPH_BLACKHAT, element);
    imshow("黑帽操作", dst);
}
// 黑帽操作
int main()
{    
    src = imread("D:/Project_Images/pencil2.jpeg");
    if (src.empty())
    {
        cout << "no image" << endl;
        return -1;
    }
    imshow("原图像", src);
    namedWindow("黑帽操作");
    createTrackbar("滑块调节", "黑帽操作", &val, 15, onBack);
    onBack(0, 0);
    waitKey(0);
    return 0;
}

效果:

可以突出小的黑色边缘。

3. 图像金字塔

解释:图像金字塔是对图像多种尺度(分辨率)的统称。

图像的放大或缩小则体现金字塔的变化过程;

缩小的过程叫做下采样,如果接触过深度学习CNN,也叫池化操作,起到压缩图像画质,提取关键信息,得到的特征图起到降维操作,减少CNN学习的参数;

放大的过程叫做上采样,在CNN中也经常用来还原图像。

3.1 高斯金字塔

原理简单,通过高斯模糊(高斯滤波)上采样和下采样。下采样通过抛去图像中偶数行和列实现,长宽各减少为1/2,面积为1/4。上采样相反,长宽为2倍。

方法:pyrDown函数用于降采样,pyrUp函数用于上采样。

示例:

int main()
{
    Mat src = imread("D:/Project_Images/cat2.jpeg");
    Mat dst1, dst2;
    pyrDown(src, dst1, Size(src.cols / 2, src.rows / 2));
    pyrDown(dst1, dst2, Size(dst1.cols / 2, dst1.rows / 2));
    imshow("原图像", src);
    imshow("一次缩小", dst1);
    imshow("二次缩小", dst2);
    waitKey(0);
    return 0;
}

效果:

3.2 拉普拉斯金字塔

拉普拉斯金字塔(Laplacian Pyramid)是一种多尺度图像表示方法,它通过高斯金字塔构建,并在每一层保存图像的细节信息。

构建Laplace金字塔的步骤如下:

  • 构建高斯金字塔:

    • 通过高斯滤波和降采样生成不同分辨率的图像,形成高斯金字塔。
    • 记高斯金字塔为{G0​,G1​,G2​,…,Gn​},其中 G0​ 是原始图像。
  • 生成拉普拉斯金字塔:

    • 拉普拉斯金字塔的每一层 Li​ 表示高斯金字塔中相邻两层图像的差异。
    • 具体地,对于每一层 i∈[0,n−1],计算:

      Li​=Gi​−pyrUp(Gi+1​)

      其中,pyrUp(Gi+1​) 是将Gi+1​ 图像上采样到与 Gi​ 相同的尺寸。
  • 最后一层:
    • 拉普拉斯金字塔的最后一层 Ln​ 通常直接等于高斯金字塔的最底层,即:Ln​=Gn​。

比较麻烦,暂不演示程序。

Laplace金字塔作用?

答:高斯金字塔还能拉伸图像,那研究Laplace金字塔,其实就是记录上采样中丢失的细节,给记录下来。

作用示例:

图像增强--->根据Laplace金字塔保留的细节增强图像边缘、纹理细节;

图像融合--->多张曝光图合成HDR高动态范围图像

图像修复和超分辨率--->根据保留的细节实现修复和超分效果。

3.3 高斯差分

Difference of Gaussians,简称“DOG”,指把同一幅图像在不同参数下做到高斯模糊之后的结果相减。可用于图像灰度增强和角点检测等。

不同大小的核即为不同大小的高斯卷积核(滤波器)。

在本系列专栏博客(4)讲中,高斯卷积核起到的作用为平滑去噪,实现的效果也可叫做高斯模糊。

高斯差分,将2张不同高斯核kernel处理过的图像作差即为差分图。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值