Learn OpenCV ---- 大津法(Otsu‘s)阈值

本文介绍了图像阈值化的基本概念,特别是大津法(Otsu's)阈值算法。大津法是一种自动确定阈值的全局阈值算法,适用于双峰图像。通过计算最小化加权方差的阈值,将图像分割为前景和背景。在OpenCV中,我们可以直接使用内置函数实现大津法阈值处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阅读原文

什么是图像阈值

    图像阈值化是一种基于像素强度大图像二值化方法。这种方法的输入通常是一个灰度图和一个阈值,输出是一个二值图像。
    如果图像中某个像素的强度大于阈值,则该像素被标记为白色(前景);如果图像中的某个像素的强度小于或者等于阈值,则输出像素位置被标记为黑色(背景)。
    图像阈值通常作为预处理步骤,用于使得更为关注的信息突出化。
    简单的图像阈值可以通过手动来设置阈值,进而得到一个好的图像阈值表示。但是这对于大量的图像来说,这是及其乏味和无效的。
    为了克服这一缺点,大津阈值算法被提出,它是一种根据图像来自动确定阈值的算法。在深入了解大津算法之前,我们先了解图像阈值设置与图像分割之间的关系。

图像阈值与图像分割

    图像分割是指将图像分割成不同区域或像素组的一类算法。因此,在某种意义上,图像阈值是图像分割的一种简单形式,即将图像分成两组,一组为前景,一组为背景。下图给出来图像分割的一个小家族。
在这里插入图片描述

    图像阈值可以分为局部阈值算法和全局阈值算法。对于全局阈值算法而言,是使用某个固定的阈值来对整个图像进行处理;而局部阈值算法可以利用图像的局部区域信息来选择不同的阈值。值得注意的是,大津算法属于全局阈值算法。

大津(Otsu)算法

    全自动的全局阈值算法通常由下述几步组成:

  1. 预处理输入图像。
  2. 获取图像直方图(像素分布)。
  3. 计算阈值 T T T
  4. 将图像中像素大于 T T T的区域替换成白色,其余部分替换成黑色。

    可以看到,最主要的步骤在第三步,因此,不同的算法在第三步中有很大的不同。大津法在处理图像直方图后,通过最小化每个类上的方差来分割目标。通常,这种技术为双峰图像产生适当的结果。该类图像的直方图中有两个表达清晰的峰值,代表不同强度值的范围。其核心思想是将图像直方图划分为两个类,并定义一个阈值,使这些类的加权方差 σ w 2 ( t ) \sigma^2_w(t) σw2(t)最小。该加权方差的计算公式为: σ w 2 ( t ) = w 1 ( t ) σ 1 2 ( t ) + w 2 ( t ) σ 2 2 ( t ) \sigma^2_w(t)=w_1(t)\sigma^2_1(t)+w_2(t)\sigma^2_2(t) σw2(t)=w1(t)σ12(t)+w2(t)σ22(t),其中,两个权值是两个类别的概率除以阈值 t t t,该阈值在[0, 255]范围内取值,计算公式如下:
在这里插入图片描述
在这里插入图片描述
其中, P ( i ) P(i) P(i)的计算公式如下:‘
在这里插入图片描述
其中, n n n为像素的强度。接着,我们需要求公式中的两个方差,这两个方差的计算公式如下:
在这里插入图片描述
在这里插入图片描述
可以看到,如果我们想要求得两个方差,我们需要先获得 μ \mu μ的值:
在这里插入图片描述
在这里插入图片描述
自此,我们就可以得到了所需的加权方差。其实,还有另一种计算方法,这种方法也是更为常用,如下:
在这里插入图片描述
具体代码如下:

import cv2
import numpy as np


def otsu_implementation(img_title="boat.jpg", is_normalized=False, is_reduce_noise=False):
    # 使用灰度模式读取图像
    image = cv2.imread(img_title, 0)

    # 是否使用高斯模糊
    # 这里不使用
    if is_reduce_noise:
        image = cv2.GaussianBlur(image, (5, 5), 0)

    # 设置直方图bin的数量
    bins_num = 256

    # 获取图像的直方图
    hist, bin_edges = np.histogram(image, bins=bins_num)

    # 是否对图像进行正则化
    # 这里不进行
    if is_normalized:
        hist = np.divide(hist.ravel(), hist.max())

    # 计算bin的中心
    bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2.

    # 迭代累加得到权值
    weight1 = np.cumsum(hist)
    weight2 = np.cumsum(hist[::-1])[::-1]

    # 计算得到均值
    mean1 = np.cumsum(hist * bin_mids) / weight1
    mean2 = (np.cumsum((hist * bin_mids)[::-1]) / weight2[::-1])[::-1]

    # 计算最终方差
    inter_class_variance = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2

    # 取最大值对应的下标
    index_of_max_val = np.argmax(inter_class_variance)
    # 从bin中取得阈值
    threshold = bin_mids[:-1][index_of_max_val]
    print("Otsu's algorithm implementation thresholding result: ", threshold)
    return threshold


def main():
    otsu_implementation()


if __name__ == "__main__":
    main()

当然,Opencv也提供了大津算法来进行图像阈值的计算,因此,我们在此也给出使用了Opencv接口的代码:

import cv2
from matplotlib.ticker import FuncFormatter
from matplotlib import pyplot as plt
from otsu_implementation import otsu_implementation


def call_otsu_threshold(img_title="boat.jpg", is_reduce_noise=False):
    # 读取二值图像
    image = cv2.imread(img_title, 0)

    if is_reduce_noise:
        image = cv2.GaussianBlur(image, (5, 5), 0)

    # 现实图像的直方图
    plt.hist(image.ravel(), 256)
    plt.xlabel('Colour intensity')
    plt.ylabel('Number of pixels')
    plt.savefig("image_hist.png")
    plt.show()
    plt.close()

    # 图像二值化,其中THRESH_OTSU就为大津算法
    otsu_threshold, image_result = cv2.threshold(
        image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU,
    )
    print("Obtained threshold: ", otsu_threshold)

    # 显示两类大概率的直方图
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.hist(image_result.ravel(), 256)
    ax.set_xlabel('Colour intensity')
    ax.set_ylabel('Number of pixels')
    ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: ('%1.1fM') % (x*1e-6)))
    plt.savefig("image_hist_result.png")
    plt.show()
    plt.close()

    # 将二值化后的图像显示
    cv2.imshow("Otsu's thresholding result", image_result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def main():
    call_otsu_threshold()
    otsu_implementation()


if __name__ == "__main__":
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值