Python opencv图像处理基础总结(四) 模板匹配 图像二值化

2022-05-09 08:07:05 浏览数 (1)

文章目录

  • 一、模板匹配
    • 1. 匹配原理
    • 2. 匹配算法
    • 3. opencv相关API
  • 二、图像二值化
    • 1. 全局阈值函数
    • 2. 局部阈值函数

一、模板匹配

1. 匹配原理

模板匹配是一种最原始、最基本的模式识别方法,研究某一特定对象物的图案位于图像的什么地方,进而识别对象物,这就是一个匹配问题。它是图像处理中最基本、最常用的匹配方法。模板匹配具有自身的局限性,主要表现在它只能进行平行移动,若原图像中的匹配目标发生旋转或大小变化,该算法无效。

  • 模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域
  • 所以模板匹配首先需要一个模板图像(给定的子图像)
  • 另外需要一个待检测图像—源图像
  • 在待检测图像上,从左到右,从上向下,计算模板图像与重叠子图像的匹配度,匹配度越高,两者相同的可能性越大。

2. 匹配算法

  • TM_SQDIFF是平方差匹配;TM_SQDIFF_NORMED是标准平方差匹配。利用平方差来进行匹配,最好匹配为0;匹配越差,匹配值越大。
  • TM_CCORR是相关性匹配;TM_CCORR_NORMED是标准相关性匹配。采用模板和图像间的乘法操作,数越大表示匹配程度较高,0表示最坏的匹配效果。
  • TM_CCOEFF是相关性系数匹配;TM_CCOEFF_NORMED是标准相关性系数匹配。将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列)。

从简单的测量(平方差)到更复杂的测量(相关系数),可获得越来越准确的匹配(同时也意味着越来越大的计算代价)。

3. opencv相关API

opencv的目标匹配函数

代码语言:javascript复制
cv2.matchTemplate(image, templ, method, result=None, mask=None) -> result

mage参数表示待检测源图像,必须是8位整数或32位浮点。
templ参数表示模板图像,必须不大于源图像并具有相同的数据类型。
method参数表示计算匹配程度的方法。
result参数表示匹配结果图像,必须是单通道32位浮点。如果image的尺寸为W x H,templ的尺寸为w x h,则result的尺寸为(W-w 1)x(H-h 1)。

opencv的函数minMaxLoc:在给定的矩阵中寻找最大和最小值,并给出它们的位置。 该功能不适用于多通道阵列,如果需要在所有通道中查找最小或最大元素,要先将阵列重新解释为单通道。

代码语言:javascript复制
minMaxLoc(src, mask=None) -> minVal, maxVal, minLoc, maxLoc

src参数表示输入单通道图像
mask参数表示用于选择子数组的可选掩码
minVal参数表示返回的最小值,如果不需要,则使用NULL。
maxVal参数表示返回的最大值,如果不需要,则使用NULL。
minLoc参数表示返回的最小位置的指针(在2D情况下); 如果不需要,则使用NULL。
maxLoc参数表示返回的最大位置的指针(在2D情况下); 如果不需要,则使用NULL。

opencv的函数 rectangle 用于绘制矩形

代码语言:javascript复制
 cv2.rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None) -> img
 
img参数表示源图像
pt1参数表示矩形的一个顶点(左上)
pt2参数表示与pt1相对的对角线上的另一个顶点(右下)
color参数表示矩形线条颜色 (RGB) 或亮度(灰度图像)
thickness参数表示组成矩形的线条的粗细程度,取负值时(如 CV_FILLED)函数绘制填充了色彩的矩形。
lineType参数表示线条的类型
shift参数表示坐标点的小数点位数

代码如下:

代码语言:javascript复制
import cv2 as cv
import numpy as np


def template_matching():
    sample = cv.imread(r'./test/031.png')    # 模板图像
    target = cv.imread(r'./test/030.jpg')    # 待检测图像
    cv.imshow('sample', sample)
    cv.imshow('target', target)
    # 三种模板匹配算法
    methods = [cv.TM_SQDIFF_NORMED, cv.TM_CCORR_NORMED, cv.TM_CCOEFF_NORMED]
    height, width = sample.shape[:2]   # 模板图像的高 宽
    for method in methods:
        print(method)
        result = cv.matchTemplate(image=target, templ=sample, method=method)  # 计算那个区域匹配最好
        # 在匹配的结果中寻找   最小值  最大值及最小、最大值的位置
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
        if method == cv.TM_SQDIFF_NORMED:   # 如果是标准平方差匹配  取最小值位置
            left_top = min_loc
        else:
            left_top = max_loc
        right_bottom = (left_top[0]   width, left_top[1]   height)  # 加上宽  高
        # 匹配到最佳位置    画小矩形
        cv.rectangle(img=target, pt1=left_top, pt2=right_bottom, color=(0, 0, 255), thickness=2)
        cv.imshow('match-'   np.str(method), target)

template_matching()
cv.waitKey(0)
cv.destroyAllWindows()

运行效果如下:

模板图像

匹配结果如下:

二、图像二值化

在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。

该函数的阈值操作属于像素级的操作,在灰度图中,每个像素都对应一个灰度值(0~255,0黑、255白),我们将阈值函数 threshold() 应用于图像,图像的灰度值与阈值进行比较,从而实现二值化处理,目的是滤除太大或太小值像素、消除噪声,从而从灰度图中获取二值图像(将图像的灰度值设置为0或255),实现增强整个图像呈现更为明显的黑白效果,同时也大大减少了数据量。

代码语言:javascript复制
import cv2 as cv
import numpy as np


# 全局
def threshold_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    ret, binary = cv.threshold(src=gray, thresh=0, maxval=255, type=cv.THRESH_BINARY | cv.THRESH_OTSU)
    # ret, binary = cv.threshold(src=gray, thresh=0, maxval=255, type=cv.THRESH_BINARY | cv.THRESH_TRIANGLE)
    # ret, binary = cv.threshold(src=gray, thresh=115, maxval=255, type=cv.THRESH_BINARY)
    # ret, binary = cv.threshold(src=gray, thresh=127, maxval=255, type=cv.THRESH_BINARY_INV)  # 相反
    # ret, binary = cv.threshold(src=gray, thresh=127, maxval=255, type=cv.THRESH_TRUNC)
    # ret, binary = cv.threshold(src=gray, thresh=130, maxval=255, type=cv.THRESH_TOZERO)
    print(f'threshold value:{ret}')    # 阈值    看图像信息丢失情况
    cv.imshow('threshold binary image', binary)


# 局部  自适应阈值
def local_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    # dst = cv.adaptiveThreshold(src=gray, maxValue=255, adaptiveMethod=cv.ADAPTIVE_THRESH_MEAN_C,
    #                      thresholdType=cv.THRESH_BINARY, blockSize=25, C=10)     # blockSize必须是奇数  C 常量
    dst = cv.adaptiveThreshold(src=gray, maxValue=255, adaptiveMethod=cv.ADAPTIVE_THRESH_GAUSSIAN_C,
                               thresholdType=cv.THRESH_BINARY, blockSize=25, C=10)  # blockSize必须是奇数  C 常量

    cv.imshow('local binary image', dst)

# 自定义   均值作为阈值
def custom_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    h, w = gray.shape[:2]
    m = np.reshape(gray, [1, h * w])
    mean = m.sum() / (w * h)
    print(f'mean:{mean}')
    ret, binary = cv.threshold(gray, mean, 255, cv.THRESH_BINARY)
    cv.imshow('custom binary image', binary)



src = cv.imread(r'./test/014.png')
cv.imshow('input image', src)
threshold_image(src)
local_image(src)
custom_image(src)
cv.waitKey(0)
cv.destroyAllWindows()

1. 全局阈值函数

代码语言:javascript复制
cv2.threshold(src, thresh, maxval, type, dst=None)

src - 输入图像(多通道,8位或32位浮点)
thresh - 阈值
maxval - 最大值
type - 阈值类型
dst - 输出图像(与src相同大小和类型以及相同通道数的数组/图像)

阈值类型

  • cv2.THRESH_BINARY 二值阈值化 —— 像素值大于阈值的设为最大值,小于阈值的设为最小值。
  • cv2.THRESH_BINARY_INV 反向二值阈值化 —— 像素值大于阈值的设为最小值,小于阈值的设为最大值。
  • cv2.THRESH_TRUNC 截断阈值化 —— 像素值大于阈值的设为阈值,小于阈值的保持原来的像素值。
  • cv2.THRESH_TOZERO 超过阈值被置0 —— 像素值大于阈值的置为0,小于阈值的保持原来的像素值。
  • cv2.THRESH_TOZERO_INV 像素值大于阈值的保持原来的像素值,小于阈值的置为0。

这些函数都有两个返回值,第一个返回值为使用的阈值,第二个就是阈值化后的图像。

最大类间方差法(OTSU算法 大津法)

对于图像二值化的简单阈值法,我们需要自己提供一个阈值,而最大类间方差法可以根据图像特性,选择最佳的阈值,故它也被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。按照最大类间方差法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。

它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

  • 应用:是求图像全局阈值的最佳方法,应用不言而喻,适用于大部分需要求图像全局阈值的场合。
  • 优点:计算简单快速,不受图像亮度和对比度的影响。
  • 缺点:对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。

代码如下:

代码语言:javascript复制
import cv2 as cv


# 全局
def threshold_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    ret, binary = cv.threshold(src=gray, thresh=0, maxval=255, type=cv.THRESH_BINARY | cv.THRESH_OTSU)
    print(f'threshold value:{ret}')    # 阈值    看图像信息丢失情况
    cv.imshow('threshold binary image', binary)


# 局部  自适应阈值
def local_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

    dst = cv.adaptiveThreshold(src=gray, maxValue=255, adaptiveMethod=cv.ADAPTIVE_THRESH_GAUSSIAN_C,
                               thresholdType=cv.THRESH_BINARY, blockSize=25, C=10)  # blockSize必须是奇数  C 常量

    cv.imshow('local binary image', dst)


src = cv.imread(r'./test/014.png')
cv.imshow('input image', src)
threshold_image(src)
local_image(src)
cv.waitKey(0)
cv.destroyAllWindows()

运行效果如下:

2. 局部阈值函数

全局阈值法对于某些光照不均的图像,这种全局阈值分割的效果不好。 而利用局部阈值法,根据图像上的每一个小区域计算与其对应的阀值。因此在同一幅图像上的不同区域采用的是不同的阀值,从而使我们在亮度不同的情况下得到更好的结果。

代码语言:javascript复制
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None): 

src - 输入图像(8位单通道图像)
maxValue - THRESH_BINARY 和 THRESH_BINARY_INV 的最大值
adaptiveMethod - 自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C)
thresholdType - 阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型
blockSize - 块大小(奇数且大于1)
C - 常量,从平均值或加权平均值中减去的数

代码如下:

代码语言:javascript复制
import cv2 as cv
import numpy as np


# 全局
def threshold_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    ret, binary = cv.threshold(src=gray, thresh=0, maxval=255, type=cv.THRESH_BINARY | cv.THRESH_OTSU)
    print(f'threshold value:{ret}')    # 阈值    看图像信息丢失情况
    cv.imshow('threshold binary image', binary)


# 局部  自适应阈值
def local_image(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    dst = cv.adaptiveThreshold(src=gray, maxValue=255, adaptiveMethod=cv.ADAPTIVE_THRESH_GAUSSIAN_C,
                               thresholdType=cv.THRESH_BINARY, blockSize=25, C=10)  # blockSize必须是奇数  C 常量

    cv.imshow('local binary image', dst)


src = cv.imread(r'./test/032.png')
cv.imshow('input image', src)
threshold_image(src)
local_image(src)
cv.waitKey(0)
cv.destroyAllWindows()

运行效果如下:

0 人点赞