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

1. 阈值分割

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

传统图像分割方法.png

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

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

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

  • THRESH_BINARY

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

dst(x,y)= \left\{\begin{matrix} maxval, if src(x,y) > thresh\\ 0, otherwise \end{matrix}\right.

  • THRESH_BINARY_INV

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

dst(x,y)= \left\{\begin{matrix} 0, if src(x,y) > thresh\\ maxval, otherwise \end{matrix}\right.

  • THRESH_TRUNC

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

dst(x,y)= \left\{\begin{matrix} threshold, if src(x,y) > thresh\\ src(x,y) , otherwise \end{matrix}\right.

  • THRESH_TOZERO

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

dst(x,y)= \left\{\begin{matrix} src(x,y), if src(x,y) > thresh\\ 0, otherwise \end{matrix}\right.

  • THRESH_TOZERO_INV

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

dst(x,y)= \left\{\begin{matrix} 0, if src(x,y) > thresh\\ src(x,y), otherwise \end{matrix}\right.

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

#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;
}
五种阈值分割(一).png
五种阈值分割(二).png

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

2. 全局阈值分割

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

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

g(x,y)= \left\{\begin{matrix} 1, if src(x,y) > T\\ 0, otherwise \end{matrix}\right.

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

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

2.1 OTSU 算法

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

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

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

算法的推导过程如下:

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

  2. 每一部分的平均灰度值\mu_0、\mu_1,计算每一部分的所占比例w_0、w_1,以及总的平均灰度值\mu

w_0 = N_0/ M*N

w_1 = N_1/M*N

并且 N_0 + N_1 = M*N

则,w_0 + w_1 = 1

\mu = \frac{\mu_0*N_0 + \mu_1*N_1}{M*N} = \mu_0*w_0 + \mu_1*w_1

  1. 计算他们的类间方差

\delta^2 = w_0*(\mu_0-\mu)^2+w_1*(\mu_1-\mu)^2=w_0*w_1*(\mu_0-\mu_1)^2

  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
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;
}
THRESH_BINARY_INV.png

2.2 Triangle 算法

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

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

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
triangle.png

3. 局部阈值分割

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

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

它的优点:

  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;
}
ostu和自适应阈值分割(一).png
ostu和自适应阈值分割(二).png

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

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

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

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

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

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

4. 总结

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容