Opencv-Python学习笔记(四):图像阈值、平滑、滤波

  • 本篇记录学学习简单阈值,自适应阈值,Otsu’s 二值化,图像的平滑、滤波等。
  • 将学习以下函数:cv2.thresholdcv2.adaptiveThreshold,cv2.filter2D()等。

图像阈值

与名字一样,这种方法非常简单。但像素值高于阈值时,我们给这个像素赋予一个新值(可能是白色),否则我们给它赋予另外一种颜色(也许是黑色)。这个函数就是 cv2.threshhold(src,thresh,maxval,type)。这个函数的第一个参数就是原图像原图像应该是灰度图第二个参数就是用来对像素值进行分类的阈值第三个参数就是maxVal,当像素值高于(有时是小于)阈值时应该被赋予的新的像素值。 OpenCV提供了多种不同的阈值方法,这是有第四个参数来决定的。这些方法包括:

  • cv2.THRESH_BINARY               超过阈值部分取maxval,否则取0
  • cv2.THRESH_BINARY_INV       THRESH_BINARY的反转
  • cv2.THRESH_TRUNC                大于阈值部分设为阈值,否则不变
  • cv2.THRESH_TOZERO              大于阈值部分不改变,否则设为0
  • cv2.THRESH_TOZERO_INV      THRESH_TOZERO的反转

上图摘选自《学习 OpenCV》中文版,其实这些在文档中都有详细介绍了,你也可以直接查看文档。
这个函数有两个返回值,第一个为 retVal,我们后面会解释。第二个就是阈值化之后的结果图像了。
 

# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/6 20:11
# @File : 图像阈值、平滑、模糊.py
# @Project : Workspace
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


img = cv.imread("F:/Pycharm/opencv_exercises-master/images/Crystal.jpg", 0)
ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

效果图:

自适应阈值

在上面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。当时这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
这种方法需要我们指定三个参数,返回值只有一个。

  • 自适应方法 -指定如何计算阈值。
    • cv2.ADAPTIVE_THRESH_MEAN_C:阈值是邻居区域的平均值。
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的加权总和,其中权重是高斯窗口。
  • Block Size - 邻域大小(用来计算阈值的区域大小)。
  • C - 这就是是一个常数,阈值就等于的平均值或者加权平均值减去这个常数
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


img = cv.imread("F:/Pycharm/opencv_exercises-master/images/Crystal.jpg", 0)
img = cv.medianBlur(img, 5)

ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
th3 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)

titles = ['Original Image', 'Global Thresholding (v = 127)',
          'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

Otsu’s 二值化

在第一部分中我们提到过 retVal,当我们使用 Otsu 二值化时会用到它。那么它到底是什么呢?在使用全局阈值时,我们就是随便给了一个数来做阈值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。如果是一副双峰图像(简单来说双峰图像是指图像直方图中存在两个峰)呢?我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?这就是 Otsu 二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)。这里用到到的函数还是 cv2.threshold(),但是需要多传入一个参数( flag): cv2.THRESH_OTSU。这时要把阈值设为 0。然后算法会找到最优阈值,这个最优阈值就是返回值 retVal。如果不使用 Otsu 二值化,返回的retVal 值与设定的阈值相等。
下面的例子中,输入图像是一副带有噪声的图像。第一种方法,我们设127 为全局阈值。第二种方法,我们直接使用 Otsu 二值化。第三种方法,我们首先使用一个 5x5 的高斯核除去噪音,然后再使用 Otsu 二值化。看看噪音去除对结果的影响有多大吧。
 

# 官方代码
# global thresholding
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)

# Otsu's thresholding
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)

# Otsu's thresholding after Gaussian filtering
blur = cv.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)

# plot all the images and their histograms
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
          'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
          'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]

for i in range(3):
    plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
    plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
    plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
    plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
plt.show()

图像的平滑、滤波

1. 噪声:主要有三种:

椒盐噪声(Salt & Pepper):含有随机出现的黑白亮度值。

脉冲噪声:只含有随机的正脉冲和负脉冲噪声。

高斯噪声:含有亮度服从高斯或正态分布的噪声。高斯噪声是很多传感器噪声的模型,如摄像机的电子干扰噪声。

2. 滤波器:主要两类:线性和非线性

线性滤波器:使用连续窗函数内像素加权和来实现滤波,同一模式的权重因子可以作用在每一个窗口内,即线性滤波器是空间不变的。 如果图像的不同部分使用不同的滤波权重因子,线性滤波器是空间可变的。因此可以使用卷积模板来实现滤波。 线性滤波器对去除高斯噪声有很好的效果。常用的线性滤波器有均值滤波器和高斯平滑滤波器。

(1) 均值滤波器:最简单均值滤波器是局部均值运算,即每一个像素只用其局部邻域内所有值的平均值来置换.

(2) 高斯平滑滤波器是一类根据高斯函数的形状来选择权值的线性滤波器。 高斯平滑滤波器对去除服从正态分布的噪声是很有效的。

非线性滤波器:

(1) 中值滤波器:均值滤波和高斯滤波运算主要问题是有可能模糊图像中尖锐不连续的部分。 中值滤波器的基本思想使用像素点邻域灰度值的中值来代替该像素点的灰度值,它可以去除脉冲噪声、椒盐噪声同时保留图像边缘细节。 中值滤波不依赖于邻域内与典型值差别很大的值,处理过程不进行加权运算。 中值滤波在一定条件下可以克服线性滤波器所造成的图像细节模糊,而对滤除脉冲干扰很有效。

(2) 边缘保持滤波器:由于均值滤波:平滑图像外还可能导致图像边缘模糊和中值滤波:去除脉冲噪声的同时可能将图像中的线条细节滤除。 边缘保持滤波器是在综合考虑了均值滤波器和中值滤波器的优缺点后发展起来的,它的特点是: 滤波器在除噪声脉冲的同时,又不至于使图像边缘十分模糊。 过程:分别计算[i,j]的左上角子邻域、左下角子邻域、右上角子邻域、右下角子邻域的灰度分布均匀度V; 然后取最小均匀度对应区域的均值作为该像素点的新灰度值。分布越均匀,均匀度V值越小。v=<(f(x, y) - f_(x, y))^2

2D 卷积

import cv2 as cv
import numpy as np


def blur_demo(image):
    # dst = cv.blur(image, (5, 5))  # 卷积核为5x5,均值模糊
    # dst = cv.medianBlur(image, 5)  # 中值模糊,对椒盐噪声去噪较好

    # 自定义滤波器
    kernel = np.ones([5, 5], np.float32) / 25  # 内核大小
    # kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)  # 锐化算子

    # filter2D(src, ddepth(图像深度,-1表示默认和src一样深度), kernel, dst=None, anchor=None(锚点,卷积核中心), delta=None, borderType=None)
    dst = cv.filter2D(image, -1, kernel)  # 二维滤波器
    cv.imshow("blur_demo", dst)


if __name__ == '__main__':

    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/lenanoise.png")  # 读入图片放进src中
    cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    blur_demo(src)
    
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

均值滤波

这是由一个归一化卷积框完成的。他只是用卷积框覆盖区域所有像素的平均值来代替中心元素。

def blur_demo(image):
    dst = cv.blur(image, (5, 5))  # 卷积核为5x5,均值模糊
    cv.imshow("blur_demo", dst)

中值过滤

用与卷积框对应像素的中值来替代中心像素的值。这个滤波器经常用来去除椒盐噪声。前面的滤波器都是用计算得到的一个新值来取代中心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代他。他能有效的去除椒盐噪声。卷积核的大小也应该是一个奇数。

def blur_demo(image):
    # dst = cv.blur(image, (5, 5))  # 卷积核为5x5,均值模糊
    dst = cv.medianBlur(image, 5)  # 中值模糊,对椒盐噪声去噪较好

    cv.imshow("blur_demo", dst)

自定义滤波器

def blur_demo(image):
    kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)  # 锐化算子

    dst = cv.filter2D(image, -1, kernel=kernel)  # 二维滤波器
    cv.imshow("blur_demo", dst)

高斯模糊

现在把卷积核换成高斯核(简单来说,方框不变,将原来每个方框的值是相等的,现在里面的值是符合高斯分布的,方框中心的值最大,其余方框根据距离中心元素的距离递减,构成一个高斯小山包。原来的求平均数现在变成求加权平均数,全就是方框里的值)。实现的函数是 cv2.GaussianBlur()。我们需要指定高斯核的宽和高(必须是奇数)。以及高斯函数沿 X, Y 方向的标准
差。如果我们只指定了 X 方向的的标准差, Y 方向也会取相同值。如果两个标准差都是 0,那么函数会根据核函数的大小自己计算。高斯滤波可以有效的从图像中去除高斯噪音。
 

import cv2 as cv
import numpy as np


def clamp(pv):
    if pv > 255:
        return 255
    elif pv < 0:
        return 0
    else:
        return pv


def gaussian_noise(image):  # 加高斯噪声
    h, w, c = image.shape
    for row in range(h):
        for col in range(w):
            s = np.random.normal(0, 20, 3)  # normal(loc=0.0, scale=1.0, size=None),均值,标准差,大小

            b = image[row, col, 0]
            g = image[row, col, 1]
            r = image[row, col, 2]

            image[row, col, 0] = clamp(b + s[0])
            image[row, col, 1] = clamp(g + s[1])
            image[row, col, 2] = clamp(r + s[2])

    cv.imshow("gaussian_noise", image)


if __name__ == '__main__':

    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/Crystal.jpg")  # 读入图片放进src中
    cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    # blur_demo(src)
    t1 = cv.getTickCount()  # 计算加入高斯噪声时间
    gaussian_noise(src)
    t2 = cv.getTickCount()
    time = (t2 - t1)/cv.getTickFrequency()
    print("time consume: %s"%(time * 1000))

    # GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
    # ksize表示卷积核大小,sigmaX,Y表示x,y方向上的标准差,这两者只需一个即可,并且ksize为大于0的奇数
    dst = cv.GaussianBlur(src, (7, 7), 0)  # 高斯模糊,sigmaX与ksize一个为0
    cv.imshow("Gaussian blur", dst)

    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

完整工程代码:

# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/6 20:11
# @File : 图像阈值、平滑、模糊.py
# @Project : Workspace
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


# img = cv.imread("F:/Pycharm/opencv_exercises-master/images/lenanoise.png", 0)
# ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
# ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
# ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
# ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)
#
# titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
# images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
#
# for i in range(6):
#     plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
#     plt.title(titles[i])
#     plt.xticks([]), plt.yticks([])
#
# plt.show()

# img = cv.medianBlur(img, 5)
# ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
# th3 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
#
# titles = ['Original Image', 'Global Thresholding (v = 127)',
#           'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
# images = [img, th1, th2, th3]
#
# for i in range(4):
#     plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
#     plt.title(titles[i])
#     plt.xticks([]), plt.yticks([])
# plt.show()

# global thresholding
# ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
#
# # Otsu's thresholding
# ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
#
# # Otsu's thresholding after Gaussian filtering
# blur = cv.GaussianBlur(img, (5, 5), 0)
# ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
#
# # plot all the images and their histograms
# images = [img, 0, th1,
#           img, 0, th2,
#           blur, 0, th3]
# titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
#           'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
#           'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
#
# for i in range(3):
#     plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
#     plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
#     plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
#     plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
#     plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
#     plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
# plt.show()


def blur_demo(image):
    # dst = cv.blur(image, (5, 5))  # 卷积核为5x5,均值模糊
    # dst = cv.medianBlur(image, 5)  # 中值模糊,对椒盐噪声去噪较好

    # 自定义滤波器
    # kernel = np.ones([5, 5], np.float32) / 25  # 内核大小
    kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)  # 锐化算子

    # filter2D(src, ddepth(图像深度,-1表示默认和src一样深度), kernel, dst=None, anchor=None(锚点,卷积核中心), delta=None, borderType=None)
    dst = cv.filter2D(image, -1, kernel=kernel)  # 二维滤波器
    cv.imshow("blur_demo", dst)


def clamp(pv):
    if pv > 255:
        return 255
    elif pv < 0:
        return 0
    else:
        return pv


def gaussian_noise(image):  # 加高斯噪声
    h, w, c = image.shape
    for row in range(h):
        for col in range(w):
            s = np.random.normal(0, 20, 3)  # normal(loc=0.0, scale=1.0, size=None),均值,标准差,大小

            b = image[row, col, 0]
            g = image[row, col, 1]
            r = image[row, col, 2]

            image[row, col, 0] = clamp(b + s[0])
            image[row, col, 1] = clamp(g + s[1])
            image[row, col, 2] = clamp(r + s[2])

    cv.imshow("gaussian_noise", image)


if __name__ == '__main__':

    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/Crystal.jpg")  # 读入图片放进src中
    cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    # blur_demo(src)
    t1 = cv.getTickCount()  # 计算加入高斯噪声时间
    gaussian_noise(src)
    t2 = cv.getTickCount()
    time = (t2 - t1)/cv.getTickFrequency()
    print("time consume: %s"%(time * 1000))

    # GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
    # ksize表示卷积核大小,sigmaX,Y表示x,y方向上的标准差,这两者只需一个即可,并且ksize为大于0的奇数
    dst = cv.GaussianBlur(src, (7, 7), 0)  # 高斯模糊,sigmaX与ksize一个为0
    cv.imshow("Gaussian blur", dst)

    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口
发布了29 篇原创文章 · 获赞 83 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/l59565455/article/details/102248346