OpenCV 笔记(7):基于阈值的图像分割

Part11.  阈值分割

图像分割是图像进行视觉分析和模式识别的基本前提,而阈值分割是最简单的图像分割方法。阈值分割是基于灰度值或灰度值的特性来将图像直接划分为区域,实现简单而且计算速度快。

8cbe2174a3ffa390d794b2a4c8370e81.jpeg
传统图像分割方法.png

11.1 threshold() 函数的5种处理类型

前面的文章提过,OpenCV 提供了基于灰度值的阈值分割函数 threshold(),在使用 threshold() 时先要将图像灰度化。

这个 threshold() 函数提供了 5 种阈值化类型。

  • THRESH_BINARY

将小于阈值的像素点灰度值置为0;大于阈值的像素点灰度值置为最大值(255)。

  • THRESH_BINARY_INV

将大于阈值的像素点灰度值置为0;小于阈值的像素点灰度值置为最大值(255)。

  • THRESH_TRUNC

小于阈值的像素点灰度值不变;大于阈值的像素点灰度值置为该阈值。

  • THRESH_TOZERO

大于阈值的像素点灰度值不变;小于阈值的像素点灰度值置为0

  • THRESH_TOZERO_INV

小于阈值的像素点灰度值不变;大于阈值的像素点置为0

下面的例子,通过获取图像的均值作为阈值,来分别展示这五种阈值分割的使用:

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    Mat src = imread(".../landscape.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);
    Scalar m = mean(gray);
    int thresh = m[0];

    Mat dst;
    threshold(gray, dst,thresh,255, THRESH_BINARY);
    imshow("thresh_binary",dst);

    threshold(gray, dst,thresh,255, THRESH_BINARY_INV);
    imshow("thresh_binary_inv",dst);

    threshold(gray, dst,thresh,255, THRESH_TRUNC);
    imshow("thresh_trunc",dst);

    threshold(gray, dst,thresh,255, THRESH_TOZERO);
    imshow("thresh_tozero",dst);

    threshold(gray, dst,thresh,255, THRESH_TOZERO_INV);
    imshow("thresh_tozero_inv",dst);

    waitKey(0);
    return 0;
}
24d4a1e8ab94ec5dc83e89b223e7ee09.jpeg
五种阈值分割(一).png
ea5e22e0df984659918c0dbb1c19c3a7.jpeg
五种阈值分割(二).png

THRESH_BINARY 和 THRESH_BINARY_INV 可以通过阈值分割将灰度图像转变成二值图像。而通过其他的阈值方式进行分割,仍然得到灰度图像。

Part22. 全局阈值分割

对图像进行灰度化之后,若图像中的目标和背景具有不同的灰度集合,且这两个灰度集合可用一个灰度级阈值 T 进行分割。

分割后的图像 g(x,y) 满足:

当 T 是一个适用于整个图像的常数时,称为全局阈值分割。

在大多数情况下,图像之间会有较大变化,即使全局阈值分割是一种合适的方法,也需要有能对每幅图像自动估计阈值的算法。

22.1 OTSU 算法

OTSU 算法,是1979年由日本学者大津提出的,也被称为大津算法、最大类间差法。OTSU 算法在类间方差最大的情况下是最佳的,完全以在一幅图像的直方图上执行计算为基础。

直方图是一种常用的数据统计图。对某一物理或特征量不同取值,找出它的最大值和最小值,然后确定一个区间,使其包含全部测量数据,将区间分成若干小区间,统计测量结果出现在各小区间的频数或占比,以测量数据为横坐标,以频数或占比为纵坐标,划出各小区间及其对应的频数或占比高度,则可得到一个矩形图。

OTSU 算法的基本思想是根据选取的阈值将图像分为目标和背景两个部分,计算该灰度值下的类间方差值。当类间方差最大时,对应的灰度值作为最佳阈值。

算法的推导过程如下:

  1. 假设 M*N 尺寸的图像的灰度值区间为[0,m],t 为阈值,它将图像分为目标和背景两个部分。即为灰度值为 [0,t] 的背景以及灰度值为 [t+1,m] 的目标两部分。其中,背景的像素点为,目标的像素点为。

  2. 每一部分的平均灰度值、,计算每一部分的所占比例、,以及总的平均灰度值。

并且

则,

  1. 计算他们的类间方差

  1. 通过遍历,获得类间方差最大时对应的阈值t作为我们最终所取的阈值。

背景和目标之间的类间方差越大,则构成图像的这两部分的差别越大,因此可以将这个阈值来进行全局的阈值分割。

下面的例子,使用 OTSU 算法来计算阈值,并且将阈值分割后的二值图像展示出来。

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    Mat src = imread(".../landscape.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    Mat dst;
    int thresh = threshold(gray, dst,0,255, THRESH_BINARY | THRESH_OTSU);
    imshow("thresh_ostu",dst);

    cout << "thresh = " << thresh << endl;

    waitKey(0);
    return 0;
}

输出结果:

thresh = 118
d4fb804f5f378152a6740649c0915e46.jpeg
ostu.png

在使用 threshold() 函数时,如果使用 THRESH_BINARY类型来进行阈值分割,通常需要黑色的背景。如果是白色的背景,则需要使用 THRESH_BINARY_INV类型。例如下面的图像是白色的背景:

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    Mat src = imread(".../phone.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    Mat dst;
    threshold(gray, dst,0,255, THRESH_BINARY_INV | THRESH_OTSU);
    imshow("dst",dst);

    waitKey(0);
    return 0;
}
9ff5858535dae543e48a0d181c4c580c.jpeg
THRESH_BINARY_INV.png

32.2 Triangle 算法

OTSU 算法是针对直方图中有两个波峰的情况,效果会比较好。但是针对直方图中只有一个波峰的情况,则 Triangle 算法会比较好。

它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度值即为阈值。

ec2a0046341cc4ffb189500706c967f9.jpeg
Triangle算法.png

下面的例子,使用 Triangle 算法来计算阈值,并且将阈值分割后的二值图像展示出来。

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    Mat src = imread(".../landscape.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    Mat dst;
    int thresh = threshold(gray, dst,0,255, THRESH_BINARY | THRESH_TRIANGLE);
    imshow("thresh_triangle",dst);

    cout << "thresh = " << thresh << endl;

    waitKey(0);
    return 0;
}

输出结果:

thresh = 80
ecf965f3ddcae20671373d6d45f8c5d0.jpeg
triangle.png

Part33. 局部阈值分割

我们在使用灰度阈值分割图像的时候,会受到噪声、光照、反射的影响。在这种情况下,整幅图像用一个固定的阈值来分割,可能得不到好的分割效果。我们可以对图像降噪,以及使用可变阈值近似处理照明和反射引起的不均匀性。

后续的文章会单独介绍如何对图像降噪,在这里我们介绍一种自适应阈值分割的方法。它的阈值是根据图像上的每一个小区域计算与其对应的阈值,在同一幅图像上的不同区域采用的是不同的阈值,根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。

它的优点:

  1. 每个像素位置处的二值化阈值是由其周围邻域像素的分布来决定的。

  2. 不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值。

  3. 适合处理光照不均的图像。

下面的例子分别使用 OTSU 算法和自适应阈值分割来实现二值化,其中 OpenCV 提供了 adaptiveThreshold() 函数实现自适应阈值分割。

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    Mat src = imread(".../viaduct.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    Mat dst;
    threshold(gray, dst,0,255, THRESH_BINARY | THRESH_OTSU);
    imshow("thresh_ostu",dst);

    adaptiveThreshold(gray, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2);
    imshow("adaptiveThreshold",dst);

    waitKey(0);
    return 0;
}
a7a0eaf6a2831e2c456b87acacb60524.jpeg
ostu和自适应阈值分割(一).png
79c1d28cc458091fd11438709f850205.jpeg
ostu和自适应阈值分割(二).png

稍微对 adaptiveThreshold() 函数的参数做一下解释:

第四个参数 adaptiveMethod:指定自适应阈值算法。

  • ADAPTIVE_THRESH_MEAN_C:局部邻域块的平均值。该算法是先求出块中的均值,再减去常数 c。

  • ADAPTIVE_THRESH_GAUSSIAN_C:局部邻域块的高斯加权和。该算法是在区域中 (x,y) 周围的像素根据高斯函数按照他们离中心点的距离进行加权计算, 再减去常数 c。

第六个参数 blockSize:邻域块大小。

第七个参数 c:与算法有关的参数,阈值就等于计算出的平均值或者加权平均值减去这个常数,c 可以是负数。

Part44.  总结

本文介绍了传统图像分割的方法,主要是介绍了基于灰度图像的阈值分割。

阈值分割并不等同于图像的二值化, threshold() 函数有五种阈值类型,它适合全局的阈值分割。对于光照不均的图像可以采用 adaptiveThreshold() 函数进行自适应阈值分割。

Java与Android技术栈】公众号

关注 Java/Kotlin 服务端、桌面端 、Android 、机器学习、端侧智能

更多精彩内容请关注:

猜你喜欢

转载自blog.csdn.net/SLFq6OF5O7aH/article/details/134521788
今日推荐