OpenCV中的图像处理

本文介绍了OpenCV中颜色空间转换,特别是BGR到HSV的应用,以及如何利用HSV追踪特定颜色的对象。此外,详细讲解了图像的几何变换(缩放、平移、旋转、仿射和透视变换),并探讨了图像阈值处理,包括简单阈值、自适应阈值和Otsu的二值化方法。
摘要由CSDN通过智能技术生成

改变颜色空间

OpenCV中有超过150种颜色空间转换方法,其中两个最广泛使用的为BGR<->灰色、BGR<->HSV。
使用cvtColor(input_image, flag)进行颜色转换,其中flag决定转换的类型。对于BGR<->灰色,使用标志cv.COLOR_BGR2GRAY;对于BGR<->HSV,使用标志cv.COLOR_BGR2HSV。要获取其他标志,可使用以下代码:

import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)

注意:HSV的色相范围为[0, 179],饱和度范围为[0, 255],值范围为[0, 255],不同的软件使用不同的规模,因此要将OpenCV值和它们比较,需要将这些范围标准化。

对象追踪

知道如何将BGR图像转换为HSV,可以使用它来提取一个有颜色的对象。在HSV中比在BGR颜色空间中更容易表示颜色。在以下程序中,尝试提取一个蓝色的对象。方法如下:取视频的每一帧->转换从BGR到HSV颜色空间->对HSV图像设置蓝色范围的阈值->单独提取蓝色对象。

import cv2 as cv
import numpy as np
cap = cv.VideoCapture(0)
while(1):
    #读取帧
    _, frame = cap.read()
    #转换颜色空间BGR到HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    #定义HSV中蓝色的范围
    lower_blue = np.array([110, 50, 50])
    upper_blue = np.array([130, 255, 255])
    # 设置HSV的阈值使得只取蓝色
    mask = cv.inRange(hsv, lower_blue, upper_blue)
    # 将掩膜和图像逐像素相加
    res = cv.bitwise_and(frame, frame, mask = mask)
    cv.imshow('frame', frame)
    cv.imshow('mask', mask)
    cv.imshow('res', res)
    k = cv.waitKey(5) & 0xff
    if k == 27:
        break
cv.destroyAllWindows

图像中的一些噪点,会在后续章节中学习如何删除它们。

如何找到要追踪的HSV值

使用cv.cvtColor(),只需传递想要的BGR值,而不是传递图像。如要查找绿色的HSV值,可使用如下代码:

import cv2 as cv
import numpy as np

green = np.uint8([[[0, 255, 0]]])
hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
print(hsv_green)

结果为:[[[ 60 255 255]]]
可以把[H - 10, 100, 100]和[H + 10, 255, 255]分别作为下界和上界。除了这种方法外,可以使用任何图像编辑工具(如GIMP或任何在线转换器)来查找这些值,但不要忘记调整HSV范围。

图像的几何变换

变换

OpenCV提供了两个转换函数cv.warpAffinecv.warpPerspective,可以使用它们进行各种转换。cv.warpAffine采用2x3转换矩阵、cv.warpPerspective采用3x3转换矩阵作为输入。

缩放

缩放只是调整图像的大小。OpenCV带有一个函数cv.resize(),图像的大小可以手动指定,也可以指定缩放比例,可以使用不同的插值方法。首选的插值方法是cv.INTER_AREA用于缩小,cv.INTER_CUBIC(慢)和cv.INTER_LINEAR用于缩放。默认情况下,出于所有调整大小的目的,使用的插值方法为cv.INTET_LINEAR。可使用以下方法调整输入图像的大小:

import cv2 as cv
import numpy as np

img = cv.imread('./OpenCV/fusi1.jpg')
# res = cv.resize(img, None, fx = 2, fy = 2, interpolation = cv.INTERcUBIC)
# 或者
height, width = img.shape[:2]
res = cv.resize(img, (2 * width, 2 * height), interpolation = cv.INTER_CUBIC)
cv.imshow('img', img)
cv.imshow('res', res)   #缩放后
k = cv.waitKey(0) & 0xff
if k == 27:
    cv.destroyAllWindows

平移

平移是物体位置的移动。若知道在(x, y)方向上的位移,则将其设为 ( t x , t y ) , (t_{x} , t_{y}) , (tx,ty),可以创建转换矩阵M,如下所示:
M = [ 1 0 t x 0 1 t y ] M=\begin{bmatrix} 1 & 0 & t_{x} \\ 0 & 1 & t_{y} \end{bmatrix} M=[1001txty]
可以将其放入np.float32类型的Numpy数组中,并将其传递给cv.warpAffine函数。如下列示例代码偏移为(100, 50):

import cv2 as cv
import numpy as np

img = cv.imread('./OpenCV/fusi1.jpg', 0)
rows, cols = img.shape
M = np.float32([[1, 0, 100], [0, 1, 50]])
dst = cv.warpAffine(img, M, (cols, rows))
cv.imshow('img', dst)
k = cv.waitKey(0) & 0xff
if k == 27:
    cv.destroyAllWindows

在这里插入图片描述
cv.warpAffine函数的第三个参数是输出图像的大小,其形式应为(width, height),其中width = 列数,height = 行数。

旋转

图像旋转角度为 θ \theta θ 是通过以下形式的变换矩阵实现的: M = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] M=\begin{bmatrix}\cos\theta & -\sin \theta \\ \sin \theta & \cos\theta \end{bmatrix} M=[cosθsinθsinθcosθ]
但是OpenCV提供了可缩放的旋转以及可调整的旋转中心,因此可以在任何为位置旋转,修改后的变换矩阵为:
[ α β ( 1 − α ) ⋅ c e n t e r . x − β ⋅ c e n t e r . y − β α β ⋅ c e n t e r . x + ( 1 − α ) ⋅ c e n t e r . y ] \begin{bmatrix} \alpha& \beta & \left(1-\alpha\right) \cdot center_{.}x - \beta \cdot center_{.} y \\ -\beta & \alpha & \beta\cdot center_{.}x + \left( 1-\alpha \right) \cdot center_{.}y \end{bmatrix} [αββα(1α)center.xβcenter.yβcenter.x+(1α)center.y]
其中: α = s c a l e ⋅ cos ⁡ θ , β = s c a l e ⋅ sin ⁡ θ \alpha = scale \cdot \cos \theta ,\beta = scale \cdot \sin \theta α=scalecosθβ=scalesinθ
为了找到此转换矩阵,OpenCV提供了cv.getRotationMatrix2D函数。以下示例代码将图像相对于中心旋转90度而没有任何缩放比例。
在这里插入图片描述

仿射变换

在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。为了找到变换矩阵,需要输入图像中的三个点及其在输出图像中的对应位置。然后cv.getAffineTransform将创建一个2x3矩阵,该矩阵将传递给cv.warpAffine

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/pane.jpg')
rows, cols, ch = img.shape
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv.getAffineTransform(pts1, pts2)
dst = cv.warpAffine(img, M, (cols, rows))
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

在这里插入图片描述

透视变换

对于透视变换,需要3x3变换矩阵。即使在转换后,直线也将保持直线。要找到此变换矩阵,需要在输入图像上有4个点,在输出图像上需要相应的点;在这4个点中,其中3个不应共线;可以通过函数cv.getPerspectiveTransform找到变换矩阵,然后将cv.warpPerspective应用于此3x3转换矩阵。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/sudoku.png')
rows, cols, ch = img.shape
pts1 = np.float32([[46, 55], [320, 41], [18, 334], [341, 341]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv.getPerspectiveTransform(pts1, pts2)
dst = cv.warpPerspective(img, M, (300, 300))
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

在这里插入图片描述

图像阈值

简单阈值

函数cv.threshold用于应用阈值,第一个参数是源图像(灰度图像);第二个参数是阈值,用于对像素值进行分类;第三个参数是分配给超过阈值的像素值的最大值;第四个参数为OpenCV提供的不同类型的阈值。
所有简单的阈值类型为:cv.THRESH_BINARYcv.THRESH_BINARY_INVcv.THRESH_TRUNCcv.THRESH_TOZEROcv.THRESH_TOZERO_INV
函数cv.threshold返回两个输出,第一个是使用的阈值,第二个输出是应用阈值后的图像。
下列代码比较了不同的简单阈值类型:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/gradient.jpg', 0)
ret, thresh1 = cv.threshold(img, 126, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img, 126, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img, 126, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img, 126, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img, 126, 255, cv.THRESH_TOZERO_INV)
imgs = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
for i in range(6):
    plt.subplot(2, 3, i + 1)
    plt.imshow(imgs[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述
从结果可以看出,
cv.THRESH_BINARY将小于阈值的像素值设置为0,大于阈值的像素值设置为255;
cv.THRESH_BINARY_INV将小于阈值的像素值设置为255,大于阈值的像素值设置为0;
cv.THRESH_TRUNC将小于阈值的像素值保持不变,大于阈值的像素值设置为阈值;
cv.THRESH_TOZERO将小于阈值的像素值设置为0,大于阈值的像素值保持不变;
cv.THRESH_TOZERO_INV将小于阈值的像素值保持不变,大于阈值的像素值设置为0。

自适应阈值

前面使用一个全局值作为阈值,但可能不是在所有情况下都很好;如图像在不同区域具有不同的光照条件,在这种情况下,自适应阈值阈值化可以;算法基于像素周围的小区域确定像素的阈值,为光照变化的图像提供了更好的结果。
cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻近区域的平均值减去常数C;
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的高斯加权总和减去常数C;
使用BLOCKSIZE确定附近区域的大小;
C是从邻域像素的平均或加权总和中减去一个常数。

Otsu的二值化

在全局阈值化中,使用任意选择的值作为阈值;而Otsu的方法避免了必须选择一个值并自动确定它的情况。考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰;一个好的阈值应该在这两个值的中间;类似地,Otsu的方法从图像直方图中确定最佳全局阈值。
为此,使用cv.threshold作为附加标志传递,阈值可以任意选择,然后算法找到最佳阈值,该阈值作为第一输出返回。
查看以下示例,输入图像为噪点图像,在第一种情况下,采用值为127的全局阈值;第二种情况下,直接采用Otsu阈值法;第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,
然后应用Otsu阈值处理,了解噪声滤波如何改善结果。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/noisy.jpg', 0)
# 全局阈值
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# Otsu阈值
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 绘制所有图像及其直方图
imags = [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(imags[i * 3], 'gray')
    plt.title(titles[ i * 3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 2), plt.hist(imags[i * 3].ravel(), 256)
    plt.title(titles[ i * 3 + 1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 3), plt.imshow(imags[i * 3 + 2], 'gray')
    plt.title(titles[ i * 3 + 2]), plt.xticks([]), plt.yticks([])
plt.show()

noisy.jpg:
noisy.jpg
运行结果:
在这里插入图片描述

Otsu的二值化如何实现?

由于正在处理双峰图像,Otsu的算法尝试找到一个阈值(t),该阈值将由关系式给出的 加权类内方差 最小化:
在这里插入图片描述
它找到位于两个峰值之间的t值,以使两个类别的差异最小。在Python中的实现如下:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/noisy.jpg', 0)
blur = cv.GaussianBlur(img, (5, 5), 0)
# 寻找归一化直方图和对应的累积分布函数
hist = cv.calcHist([blur], [0], None, [256], [0, 356])
hist_norm = hist.ravel() / hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1, 256):
    p1, p2 = np.hsplit(hist_norm, [i])  #概率
    q1, q2 = Q[i], Q[255] - Q[i]    #对类求和
    b1, b2 = np.hsplit(bins, [i])   #权重
    # 寻找均值和方差
    m1, m2 = np.sum(p1 * b1) / q1, np.sum(p2 * b2) / q2
    v1, v2 = np.sum(((b1 - m1) **2) * p1) / q1, np.sum(((b2 - m2) ** 2) * p2) / q2
    # 计算最小化函数
    fn = v1 * q1 + v2 * q2
    if fn < fn_min:
        fn_min = fn
        thresh = i
# 使用OpenCV函数找到Otsu的阈值
ret, otsu = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
print("{} {}".format(thresh, ret))

学习来源:OpenCV-Python中文文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值