Python-OpenCV自学笔记(二)图像处理

一、傅里叶变换

1.幅度谱

图像的幅度谱把一幅图像中最明亮的像素放到图像中央,然后逐渐变暗,在边缘上的像素最暗。这样可以发现图像中有多少亮的像素和暗的像素,以及它们分布的百分比。

傅里叶变换的概念是边缘检测或线段和形状检测等图像处理操作的基础。NumPy有快速傅里叶变换(FFT)的包,它包含了fft2()函数,该函数可以计算一幅图像的离散傅里叶变换(DFT)。

import cv2
import numpy as np
from matplotlib import pyplot as plot

img = cv2.imread('./1.png', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))

row, cols = img.shape
crow, ccol = row // 2, cols // 2    #python对于除法时候可能转换为浮点数,将“/”改为“//”
fshift[crow - 30: crow+30, ccol - 30: ccol + 30] = 0

f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)

plot.subplot(221), plot.imshow(img, cmap = "gray")
plot.title("Input"), plot.xticks([]), plot.yticks([])

plot.subplot(222), plot.imshow(magnitude_spectrum, cmap = "gray")
plot.title('magnitude_spectrum'), plot.xticks([]), plot.yticks([])

plot.subplot(223), plot.imshow(img_back, cmap = "gray")
plot.title("Input in JET"), plot.xticks([]), plot.yticks([])
plot.show()

Python-OpenCV自学笔记(二)图像处理_第1张图片

2.高通滤波器

高通滤波器(HPF)是检测图像的某个区域,然后根据像素与周围像素的亮度差值来提升(boost)该像素的亮度的滤波器。

核是指一组权重的集合,它会应用在源图像的一个区域,并由此生成目标图像的一个像素。比如,大小为7的核意味着每49(7 x 7)个源图像的像素会产生目标图像的一个像素。可把核看作一块覆盖在源图像上可移动的毛玻璃片,玻璃片覆盖区域的光线会按某种方式进行扩散混合后透过去。

以如下的核(kernal)(滤波器矩阵)为例:

[[0, -0.25, 0],

 [-0.25, 1, -0.25],

 [0, -0.25, 0]]

在计算完中央像素与周围邻近像素的亮度差值之和以后,如果亮度变化很大,中央像素的亮度会增加(反之则不会)。换句话说,如果一个像素比它周围的像素更突出,就会提升它的亮度。这在边缘检测上尤其有效,它会采用一种称为高频提升滤波器(high boost filter)的高通滤波器。

高通和低通滤波器都有一个称为半径(radius)的属性,它决定了多大面积的邻近像素参与滤波计算。下面是一个高通滤波器的例子。

#!/usr/bin/env python 
# -*- coding:utf-8 -*-

import cv2
import numpy as np
from scipy import ndimage
# SciPy的ndimage子模块专用于图像处理

# 创建3×3核
kernel_3x3 = np.array([[-1, -1, -1],
                   [-1,  8, -1],
                   [-1, -1, -1]])

# 创建5×5核
kernel_5x5 = np.array([[-1, -1, -1, -1, -1],
                       [-1,  1,  2,  1, -1],
                       [-1,  2,  4,  2, -1],
                       [-1,  1,  2,  1, -1],
                       [-1, -1, -1, -1, -1]])
# 滤波器中所有值加起来为0

img = cv2.imread("./1.png", 0)    # 注:以灰度模式读入图像

k3 = ndimage.convolve(img, kernel_3x3)    # 核与图像卷积
k5 = ndimage.convolve(img, kernel_5x5)
# 滤波核的维度应与原始图像的维度相同,故此采用灰度图

blurred = cv2.GaussianBlur(img, (17,17), 0)    #高斯滤波
#(5, 5)表示高斯矩阵的长与宽都是5,标准差取0
g_hpf = img - blurred

cv2.namedWindow("3x3", cv2.WINDOW_NORMAL)
cv2.imshow("3x3", k3)
cv2.namedWindow("5x5", cv2.WINDOW_NORMAL)
cv2.imshow("5x5", k5)
cv2.namedWindow("g_hpf", cv2.WINDOW_NORMAL)
cv2.imshow("g_hpf", g_hpf)
cv2.waitKey()
cv2.destroyAllWindows()


附另一位博主的结果:https://blog.csdn.net/weixin_42052460/article/details/81267294
Python-OpenCV自学笔记(二)图像处理_第2张图片

3.低通滤波器

高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。低通滤波器(Low Pass Filter, LPF)则是在像素与周围像素的亮度差值小于一个特定值时,平滑该像素的亮度。它主要用于去噪和模糊化,比如说,高斯模糊是最常用的模糊滤波器(平滑滤波器)之一,它是一个削弱高频信号信息强度的低通滤波器。

二、边缘检测

1.模糊处理函数与边缘检测滤波函数

OpenCV提供了许多边缘检测滤波函数,包括Laplacian()、Sobel()以及Scharr()。这些滤波函数都会将非边缘区域转为黑色,将边缘区域转为白色或其他饱和的颜色。但是,这些函数都很容易将噪声错误地识别为边缘。缓解这个问题的方法是在找到边缘之前对图像进行模糊处理。

OpenCV也提供了许多模糊滤波函数,包括blur()(简单的算术平均)、medianBlur()以及GaussianBlur()。边缘检测滤波函数和模糊滤波函数的参数有很多,但总会有一个ksize参数,它是一个奇数,表示滤波器的宽和高(以像素为单位)。

(1)blur()均值滤波

blur()均值滤波是一种典型的线性滤波算法,主要是利用像素点邻域的像素值来计算像素点的值。其具体方法是首先给出一个滤波kernel,该核将覆盖像素点周围的其他邻域像素点,去掉像素本身,将其邻域像素点相加然后取平均值即为该像素点的新的像素值,这就是均值滤波的本质。

(2)medianBlur()中值滤波

medianBlur()中值滤波是一种典型的非线性滤波,是基于排序统计理论的一种能够有效抑制噪声的非线性信号处理技术,基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,让周围的像素值接近真实的值从而消除孤立的噪声点。该方法在取出脉冲噪声、椒盐噪声的同时能保留图像的边缘细节。这些优良特性是线性滤波所不具备的。

medianBlur()作为模糊函数,它对去除数字化的视频噪声非常有效,特别是去除彩色图像的噪声。

(3)GaussianBlur()高斯滤波

GaussianBlur()高斯滤波是一种线性平滑滤波,对于除去高斯噪声有很好的效果。高斯滤波是通过对输入数组的每个点与输入的高斯滤波模板执行卷积计算然后将这些结果一块组成了滤波后的输出数组,通俗的讲就是高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。

我们在参考其他文章的时候可能会出现高斯模糊和高斯滤波两种说法,其实这两种说法是有一定区别的。高斯滤波是指用高斯函数作为滤波函数的滤波操作而高斯模糊是用高斯低通滤波器。

高斯滤波在图像处理中常用来对图像进行预处理操作,虽然耗时但是数字图像用于后期应用但是其噪声是最大的问题,噪声会造成很大的误差而误差在不同的处理操作中会累积传递,为了能够得到较好的图像,对图像进行预处理去除噪声也是针对数字图像处理的无奈之举。

(4)边缘检测滤波函数

三种边缘检测滤波函数介绍参考:
https://www.cnblogs.com/long5683/p/9675691.html

2.检测实现

若直接使用边缘检测函数(如Laplacian()):

import cv2

if __name__ == "__main__":
    img = cv2.imread("lena.jpg")
    cv2.Laplacian(img, cv2.CV_8U, img, ksize=5)
    cv2.imshow("img", img)
    cv2.waitKey()
    cv2.destroyAllWindows()

Python-OpenCV自学笔记(二)图像处理_第3张图片
若使用medianBlur()作为模糊函数,使用Laplacian()作为边缘检测函数,它会产生明显的边缘线条,灰度图像更是如此。在使用medianBlur()函数之后,将要使用Laplacian()函数之前,需要将图像从BGR色彩空间转为灰度色彩空间。

在得到Laplacian()函数的结果之后,需要将其转换成黑色边缘和白色背景的图像。然后将其归一化(使它的像素值在0到1之间),并乘以源图像以便能将边缘变黑。

#!/usr/bin/env python 
# -*- coding:utf-8 -*-

import cv2
from matplotlib import pyplot as plot


def stroke_edges(src, dst, blur_ksize=7, edge_ksize=5):
    if blur_ksize >= 3:
        blurred_src = cv2.medianBlur(src, blur_ksize)
        plot.subplot(222), plot.imshow(blurred_src)
        plot.title("blurred_src"), plot.xticks([]), plot.yticks([])
        gray_src = cv2.cvtColor(blurred_src, cv2.COLOR_BGR2GRAY)
    else:
        gray_src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
    cv2.Laplacian(gray_src, cv2.CV_8U, gray_src, ksize=edge_ksize)
    plot.subplot(223), plot.imshow(gray_src)
    plot.title('gray_src'), plot.xticks([]), plot.yticks([])
    normalized_inverse_alpha = (1.0 / 255) * (255 - gray_src)
    channels = cv2.split(src)
    for channel in channels:
        channel[:] = channel * normalized_inverse_alpha
    cv2.merge(channels, dst)


img = cv2.imread('./1.png')
plot.subplot(221), plot.imshow(img)
plot.title("Input"), plot.xticks([]), plot.yticks([])

stroke_edges(img, img)
plot.subplot(224), plot.imshow(img)
plot.title('Output'), plot.xticks([]), plot.yticks([])

plot.show()



注意,核的大小可由strokeEdges()函数的参数来指定。blurKsize参数会 作为medianBlur()含糊的ksize参数,edgeKsize参数会作为Laplacian()函数的ksize参数。对于作者的摄像头,将blurKsize值设为7,将edgeKsize值设为5会得到最好的效果。但对于较大的ksize(比如7),使用medianBlur()的代价很高。如果在使用strokeEdges()函数时遇到性能问题,可试着减小blurKsize的值。要关闭模糊效果,可以将blurKsize的值设为3以下。

3.Canny边缘检测

OpenCV提供了Canny函数来识别边缘。Canny边缘检测算法有5个步骤:使用高斯滤波器对图像进行去噪、计算梯度、在边缘上使用非最大抑制(NMS)、在检测到的边缘上使用双阈值去除假阳性(false positive),最后还会分析所有的边缘及其之间的连接,以保留真正的边缘并消除不明显的边缘。

import cv2

img = cv2.imread('3.png', 0)
blur = cv2.GaussianBlur(img, (3, 3), 0)  # 用高斯滤波处理原图像降噪
canny = cv2.Canny(blur, 20, 100)  # 20是最小阈值,100是最大阈值
cv2.imshow('canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('4.png',canny)

Python-OpenCV自学笔记(二)图像处理_第4张图片
上图为处理结果,上下阈值为手动调整。原图为视频截图,图片高糊,但最终处理结果相对来讲还是不错的。

三、轮廓检测

在计算机视觉中,轮廓检测不仅用来检测图像或者视频帧中物体的轮廓,而且还有其他操作与轮廓检测有关。如:计算多边形边界、形状逼近和计算感兴趣区域。这是与图像数据交互时的简单操作,因为NumPy中的矩形区域可以使用数组切片(slice)来定义。在物体检测(包括人脸)和物体跟踪时会大量使用。

1.熟悉API

#!/usr/bin/env python 
# -*- coding:utf-8 -*-

import cv2
import numpy as np

img = np.zeros((200, 200), dtype=np.uint8)  # 创建一个200x200大小的黑色空白图像,
img[50:150, 50:150] = 255  # 在图像的中央放置一个白色方块

ret, thresh = cv2.threshold(img, 127, 255, 0)  # 对图像进行二值化操作
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  # 寻找轮廓
# opencv2返回两个值:contours:hierarchy。opencv3会返回三个值,分别是img, countours, hierarchy
# openCV版本为4.1.0,返回三个值结果报错,不太明白为什么
# image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
color = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)  # 颜色空间转换
img = cv2.drawContours(color, contours, -1, (0, 255, 0), 2)  # 画出轮廓,-1,表示所有轮廓,画笔颜色为(0, 255, 0),即Green,粗细为3
cv2.imshow("contours", color)
cv2.waitKey()
cv2.destroyAllWindows()

(1)cv2.threshold():固定阈值二值化

retval, dst = cv.threshold( src, thresh, maxval, type[, dst] )

src: 输入图,只能输入单通道图像,通常来说为灰度图
thresh: 阈值
maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type: 二值化操作的类型,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV
Python-OpenCV自学笔记(二)图像处理_第5张图片
返回值:
retval: 与参数thresh一致
dst: 结果图像

(2)cv2.findContours():在二值图中查找轮廓

contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] )

  • findContours()函数有三个参数:输入图像(二值图)、层次类型和轮廓逼近方法。
  • 这个函数会修改输入图像,因此建议使用原始图像的一份拷贝(如:通过img.copy()来作为输入图像)。
  • contours:检测到的轮廓。每个轮廓都存储为点矢量。
  • hierarchy:可选输出向量,包含有关图像拓扑的信息。它具有与轮廓数量一样多的元素。对于每个第i个轮廓轮廓[i],元素层次[i] [0],层次[i] [1],层次[i] [2]和层次[i] [3]被设置为0-基于相同等级的下一轮和前轮廓的轮廓,第一轮廓和父轮廓的基础索引。如果轮廓i没有下一个,前一个,父级或嵌套轮廓,则层次结构[i]的相应元素将为负数。
  • 由函数返回的层次树相当重要:cv2.RETR_TREE参数会得到图像中轮廓的整体层次结构,以此来建立轮廓之间的“关系”。
  • 如果只想得到最外面的轮廓,可使用cv2.RETR_EXTERNAL。这对消除包含在其他轮廓中的轮廓很有用(如在大多数情形下,不需要检测一个目标包含在另一个与之相同的目标里面。
  • findContours()函数有三个返回值:修改后的图像、图像的轮廓以及它们的层次。使用轮廓来画出图像的彩色版本(即把轮廓画成绿色),并显示出来。(貌似4.1.0版本为三个返回值?求解)

mode:

  • cv.RETR_EXTERNAL:仅检索极端外轮廓。 cv.RETR_LIST:检索所有轮廓而不建立任何层次关系。
  • cv.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。在顶层,组件有外部边界。在第二层,有孔的边界。如果连接组件的孔内有另一个轮廓,它仍然位于顶层。
  • cv.RETR_TREE:检索所有轮廓并重建嵌套轮廓的完整层次结构。

method:

  • cv.CHAIN_APPROX_NONE:绝对存储所有轮廓点。轮廓任意两个连续点相邻(水平、垂直、对角线)。
  • cv.CHAIN_APPROX_SIMPLE:压缩水平,垂直和对角线段,只留下它们的端点。例如,右上方的矩形轮廓用4个点编码。
  • cv.CHAIN_APPROX_TC89_L1:应用Teh-Chin链式近似算法的一种风格。
  • cv.CHAIN_APPROX_TC89_KCOS:应用Teh-Chin链式近似算法的一种风格。

2.边界框、最小矩形区域和最小闭圆的轮廓

可用OpenCV的cv2.findContours函数找到不规则的、歪斜的以及旋转的形状。现实的应用会对目标的边界框、最小矩形面积、最小闭圆特别感兴趣。

(1)绘制矩形

下面的代码实现生成矩形,rectangle()的参数分别为输入图像、矩形左上角坐标、矩形右下角坐标、画线对应的颜色、所画的线的宽度。

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = np.zeros((512,512,3),np.uint8)  #生成一个空彩色图像
cv2.rectangle(img,(20,20),(411,411),(55,255,155),5)
plt.imshow(img,'brg')
plt.show()

(2)绘制外接几何图形

下面的代码实现对轮廓分别用矩形、最小矩形、圆包裹。

import cv2
import numpy as np

img = cv2.pyrDown(cv2.imread("hammer.jpg", cv2.IMREAD_UNCHANGED))
ret, thresh = cv2.threshold(cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
image, contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for c in contours:
    # find bounding box coordinates
    # 现计算出一个简单的边界框
    x, y, w, h = cv2.boundingRect(c)   # 将轮廓信息转换成(x, y)坐标,并加上矩形的高度和宽度
    cv2.rectangle(img, (x,y), (x+w, y+h), (0, 255, 0), 2)  # 画出矩形

    # find minimum area
    # 计算包围目标的最小矩形区域
    rect = cv2.minAreaRect(c)
    # calculate coordinate of the minimum area rectangle
    box = cv2.boxPoints(rect)
    # normalize coordinates to integers
    box =np.int0(box)
    # 注:OpenCV没有函数能直接从轮廓信息中计算出最小矩形顶点的坐标。所以需要计算出最小矩形区域,
    # 然后计算这个矩形的顶点。由于计算出来的顶点坐标是浮点型,但是所得像素的坐标值是整数(不能获取像素的一部分),
    # 所以需要做一个转换
    # draw contours
    cv2.drawContours(img, [box], 0, (0, 0, 255), 3)  # 画出该矩形

    # calculate center and radius of minimum enclosing circle
    (x, y), radius = cv2.minEnclosingCircle(c)  # 会返回一个二元组,第一个元素为圆心的坐标组成的元组,第二个元素为圆的半径值。
    # cast to integers
    center = (int(x), int(y))
    radius = int(radius)
    # draw the circle
    img = cv2.circle(img, center, radius, (0, 255, 0), 2)

cv2.drawContours(img, contours, -1, (255, 0, 0), 1)
cv2.imshow("contours", img)
cv2.waitKey()
cv2.destroyAllWindows()

Python-OpenCV自学笔记(二)图像处理_第6张图片

(3)cv2.boundingRect(img)

cv2.boundingRect(img):生成外接矩形,img是一个二值图,返回四个值,分别是x,y,w,h。x,y是矩阵左上点的坐标,w,h是矩阵的宽和高。

(4)cv2.minAreaRect(cnt)

cv2.minAreaRect(cnt):生成最小外接矩形,cnt是点集数组或向量(里面存放的是点的坐标),并且这个点集不定个数。

函数返回一个Box2D结构rect:

min_rect = ((min_rect[0][0],min_rect[0][1]),(min_rect[1][0],min_rect[1][1]),0)
#(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)
# 可以直接用min_rect[0][0]等获取相关值

但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数 cv2.cv.BoxPoints() 获得,返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]。得到的最小外接矩形的4个顶点顺序、中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)的对应关系如下:
Python-OpenCV自学笔记(二)图像处理_第7张图片
注意:旋转角度θ是水平轴(x轴)逆时针旋转,与碰到的矩形的第一条边的夹角。并且这个边的边长是width,另一条边边长是height。也就是说,在这里,width与height不是按照长短来定义的。

在opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。在这里,θ∈(-90度,0]。

你可能感兴趣的:(openCV)