OpenCV实现图像分割/二值化

图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成为图像分割中最基本和应用最广泛的分割技术。它特别适用于目标和背景占据不同灰度级范围的图像。它不仅可以极大的压缩数据量,而且也大大简化了分析和处理步骤,因此在很多情况下,是进行图像分析、特征提取与模式识别之前的必要的图像预处理过程。图像阈值化的目的是要按照灰度级,对像素集合进行一个划分,得到的每个子集形成一个与现实景物相对应的区域,各个区域内部具有一致的属性,而相邻区域不具有这种一致属性。这样的划分可以通过从灰度级出发选取一个或多个阈值来实现。

算法理论介绍

1 最大类间方差法(大津阈值法)

大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
它被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
应用: 是求图像全局阈值的最佳方法,应用不言而喻,适用于大部分需要求图像全局阈值的场合。
优点: 计算简单快速,不受图像亮度和对比度的影响。
缺点: 对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。
原理非常简单,涉及的知识点就是均值、方差等概念和一些公式推导。为了便于理解,我们从目的入手,反推一下这著名的OTSU算法。
求类间方差:
假设一幅图像有L个灰度级[1,2,…,L]。灰度级为i的像素点的个数为ni,那么总的像素点个数就应该为N=n1+n2+…+nL。为了讨论方便,我们使用归一化的灰度级直方图并且视为这幅图像的概率分布:
p i = n i / N , p i 0 , i = 1 L p i = 1 p_{i}=n_{i} / N, \quad p_{i} \geq 0, \sum_{i=1}^{L} p_{i}=1

OTSU算法的假设是存在阈值TH将图像所有像素分为两类C1(小于TH)和C2(大于TH),则这两类像素各自的均值就为m1、m2,图像全局均值为mG。同时像素被分为C1和C2类的概率分别为p1、p2。因此就有:
p 1 m 1 + p 2 m 2 = m G (1) \begin{array}{c} \mathrm{p} 1^{*} \mathrm{m} 1+\mathrm{p} 2^{*} \mathrm{m} 2=\mathrm{mG} \end{array} \tag{1} p 1 + p 2 = 1 (2) \begin{array}{c} \mathrm{p} 1+\mathrm{p} 2=1 \end{array} \tag{2}
根据方差的概念,类间方差表达式为:
σ 2 = p 1 ( m 1 m G ) 2 + p 2 ( m 2 m G ) 2 (3) \sigma^{2}=p 1(m 1-m G)^{2}+p 2(m 2-m G)^{2} \tag{3}
将式(1)带入,我们把上式化简,可得:
σ 2 = p 1 p 2 ( m 1 m 2 ) 2 (4) \sigma^{2}=p 1 p 2(m 1-m 2)^{2} \tag{4} 其实且能使得上式最大化的灰度级k就是OTSU阈值了,很多博客也是这样做的。
其中:
p 1 = i = 0 k p i (5) \begin{array}{l} p 1=\sum_{i=0}^{k} p_{i} \end{array} \tag{5} m 1 = 1 / p 1 i = 0 k i p i (6) \begin{array}{l} m 1=1 / p 1 * \sum_{i=0}^{k} i p_{i} \end{array} \tag{6} m 2 = 1 / p 2 i = k + 1 L 1 i p i (7) \begin{array}{l} m 2=1 / p 2 * \sum_{i=k+1}^{L-1} i p_{i} \end{array} \tag{7} 对照公式,遍历0-255个灰度级,使得(4)式最大既可。
根据原文,式(4)还可以进一步变形:
首先灰度级k的累加均值m图像全局均值mG分别为:
m = i = 0 k i p i (8) m=\sum_{i=0}^{k} i p_{i} \tag{8} m G = i = 0 L 1 i p i (9) m G=\sum_{i=0}^{L-1} i p_{i} \tag{9}
再看看式(6),m1、m2就可变为:
m 1 = 1 / p 1 m (10) m 1=1 / p 1 * m \tag{10}
m 2 = 1 / p 2 ( m G m ) (11) m 2=1 / p 2 *(m G-m) \tag{11}
式(10)、(11)代入式(4),可以得到原文最终的类间方差公式:
σ 2 = ( m G p 1 m ) 2 p 1 ( 1 p 1 ) (12) \sigma^{2}=\frac{(m G * p 1-m)^{2}}{p 1(1-p 1)} \tag{12}
根据公式(5)、(8)、(9)求得使得上式(12)最大化的灰度级k就是OTSU阈值。

分割:
这个分割就是二值化,OpenCV给了以下几种方式,很简单,可以参考:

2 自适应阈值

前面介绍了OTSU算法,但这算法属于全局阈值法,所以对于某些光照不均的图像,这种全局阈值分割的方法会显得苍白无力,如下图:

显然,这样的阈值处理结果不是我们想要的,那么就需要一种方法来应对这样的情况。 这种办法就是自适应阈值法(adaptiveThreshold),它的思想不是计算全局图像的阈值,而是根据图像不同区域亮度分布,计算其局部阈值,所以对于图像不同区域,能够自适应计算不同的阈值,因此被称为自适应阈值法。(其实就是局部阈值法)

如何确定局部阈值呢?可以计算某个邻域(局部)的均值、中值、高斯加权平均(高斯滤波)来确定阈值。值得说明的是:如果用局部的均值作为局部的阈值,就是常说的移动平均法。

基于OpenCV的实现

函数原型(c++)

1. 最大类间方差法

double cv::threshold (InputArray src,
                      OutputArray dst,
                      double  thresh,
                      double  maxval,
                      int type )

参数:

  • src — input array (single-channel, 8-bit or 32-bit floating point).
  • dst — output array of the same size and type as src.
  • thresh — threshold value.
  • maxval — maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding types.
  • type — thresholding type
    参考:thresholdType

2. 自适应阈值

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

参数:
Parameters

  • src — Source 8-bit single-channel image.
  • dst — Destination image of the same size and the same type as src.
  • maxValue — Non-zero value assigned to the pixels for which the condition is satisfied
  • adaptiveMethod — Adaptive thresholding algorithm to use,参考:cv::AdaptiveThresholdTypes
  • thresholdType — Thresholding type that must be either THRESH_BINARY or THRESH_BINARY_INV, 可参考:thresholdType
    blockSize Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on.
  • C — Constant subtracted from the mean or weighted mean (see the details below). Normally, it is positive but may be zero or negative as well.

实现示例(c++)

1. 大津阈值

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

int main(int argc, char* argv[])
{
 Mat img = imread(argv[1], -1);
 if (img.empty())
 {
  cout <<"Error: Could not load image" <<endl;
  return 0;
 }
 Mat gray;
 cvtColor(img, gray, CV_BGR2GRAY);
 Mat dst;
 threshold(gray, dst, 0, 255, CV_THRESH_OTSU);
 imshow("src", img);
 imshow("gray", gray);
 imshow("dst", dst);
 waitKey(0);
 return 0;
}

2. 自适应阈值

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
 Mat img = imread(argv[1], -1);
 if (img.empty())
 {
  cout <<"Error: Could not load image" <<endl;
  return 0;
 }
 Mat gray;
 cvtColor(img, gray, CV_BGR2GRAY);
 Mat dst;
 cv::adaptiveThreshold(gray,, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 21, 10);;
 imshow("src", img);
 imshow("gray", gray);
 imshow("dst", dst);
 waitKey(0);
 return 0;
}

参考

https://github.com/datawhalechina
https://blog.csdn.net/weixin_40647819

关于Datawhale

Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。

原创文章 24 获赞 31 访问量 2967

猜你喜欢

转载自blog.csdn.net/OuDiShenmiss/article/details/105850322