目录
机器视觉基础
常用图像处理技术包括色彩处理、形态相关操作、色彩梯度和轮廓处理我们将在后面内容中进行一一介绍,不过在正式开始之前,我们要先说明一下OpenCV的基础操作。
OpenCV基础操作
读取、图像、保存图像
img = cv2.imread("../data/lena.jpg", 1)[..., ::-1] # 1表示3通道彩色,0表示单通道灰度,这里的切片是因为默认读取是BGR格式,而plt.imshow(img)只能显示RGB,所以这里需要转换
print(type(img)) # 打印数据类型 #数据是class 'numpy.ndarray
print(img.shape) # 打印图像尺寸 #(300, 300, 3) 3是三通道BGR彩色图片
cv2.imwrite("../data/Linus_2.png", im) # 将图像保存到指定路径
plt.imshow(img)
plt.show()
'''这种方式也能显示图片,但是作者环境下容易内核崩溃
cv2.imshow("test", im) # 在test窗口中显示图像
cv2.waitKey() # 等待用户按键反馈
cv2.destroyAllWindows() # 销毁所有创建的窗口
'''
色彩处理
彩色图像转换为灰度图像
img = cv2.imread("../data/lena.jpg", 1)[..., ::-1]
# 使用cvtColor进行颜色空间变化,COLOR_BGR2GRAY表示BGR to GRAY
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # 彩色图像灰度化
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(img_gray, cmap=plt.get_cmap('gray'))
plt.show()
二值化与反二值化
img = cv2.imread("../data/lena.jpg", 0)
# 二值化
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 反二值化
t, rst2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
fig, axes = plt.subplots(1, 3, figsize=(12, 12))
axes[0].imshow(img, cmap=plt.get_cmap('gray'))
axes[1].imshow(rst, cmap=plt.get_cmap('gray'))
axes[2].imshow(rst2, cmap=plt.get_cmap('gray'))
plt.show()
色彩提取:提取指定的颜色
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
img = cv2.imread("../data/opencv2.png", 1)[..., ::-1]
axes[0].imshow(img)
# 取出蓝色通道,当做单通道图像显示
b = img[:, :, 2]
axes[1].imshow(b, cmap=plt.get_cmap('gray'))
# 去掉蓝色通道(索引为0的通道)
img[:, :, 2] = 0
axes[2].imshow(img)
# 去掉绿色通道(索引为1的通道)
img[:, :, 1] = 0
axes[3].imshow(img)
plt.show()
直方图均衡化:调节图像统计直方图分布
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
img = cv2.imread("../data/sunrise.jpg", 0)
axes[0].imshow(img, cmap=plt.get_cmap('gray'))
# 直方图均衡化
img_equ = cv2.equalizeHist(img)
axes[1].imshow(img_equ, cmap=plt.get_cmap('gray'))
# 绘制灰度直方图
## 原始直方图
print(img.ravel())
plt.subplot(2, 1, 1)
plt.hist(img.ravel(), #ravel返回一个连续的扁平数组
256, [0, 256], label="orig")
plt.legend()
## 均衡化处理后的直方图
plt.subplot(2, 1, 2)
plt.hist(img_equ.ravel(), 256, [0, 256], label="equalize")
plt.legend()
plt.show()
直方图均衡化:调节图像统计直方图分布
# 读取原始图片
original = cv2.imread('../data/sunrise.jpg', 1)[..., ::-1]
# BRG空间转换为YUV空间
# YUV:亮度,色度,饱和度,其中Y通道为亮度通道
yuv = cv2.cvtColor(original, cv2.COLOR_RGB2YUV)
print("yuv.shape:", yuv.shape)
yuv[..., 0] = cv2.equalizeHist(yuv[..., 0]) # 取出亮度通道,均衡化并赋回原图像
equalized_color = cv2.cvtColor(yuv, cv2.COLOR_YUV2RGB)
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(original)
axes[1].imshow(equalized_color)
plt.show()
色彩提取
img = cv2.imread('../data/opencv2.png', 1)[..., ::-1]
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
# =============指定蓝色值的范围=============
# 蓝色H通道值为120,通常取120上下10的范围
# S通道和V通道通常取50~255间,饱和度太低、色调太暗计算出来的颜色不准确
min_val = np.array([110, 50, 50])
max_val = np.array([130, 255, 255])
# 确定蓝色区域
mask = cv2.inRange(hsv, min_val, max_val) #选取出掩模
# 通过掩码控制的按位与运算,锁定蓝色区域
bule = cv2.bitwise_and(img, img, mask=mask)
plt.imshow(bule)
plt.show()
色彩处理
镜像操作
img = cv2.imread('../data/lena.jpg', 1)
flip0 = cv2.flip(img, 0) #0表示垂直方向
flip1 = cv2.flip(img, 1) #1表示水平方向
fig, axes = plt.subplots(1, 3, figsize=(12, 12))
axes[0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[1].imshow(cv2.cvtColor(flip0, cv2.COLOR_BGR2RGB))
axes[2].imshow(cv2.cvtColor(flip1, cv2.COLOR_BGR2RGB))
plt.show()
仿射变换:旋转、平移
def translate(img, x, y):
"""
坐标平移变换
:param img: 原始图像数据
:param x:平移的x坐标
:param y:平移的y坐标
:return:返回平移后的图像
"""
h, w = img.shape[:2] # 获取图像高、宽
# 定义平移矩阵
M = np.float32([[1, 0, x],
[0, 1, y]])
# 使用openCV仿射操作实现平移变换
shifted = cv2.warpAffine(img, M, (w, h)) # 第三个参数为输出图像尺寸
return shifted # 返回平移后的图像
def rotate(img, angle, center=None, scale=1.0):
"""
图像旋转变换
:param img: 原始图像数据
:param angle: 旋转角度
:param center: 旋转中心,如果为None则以原图中心为旋转中心
:param scale: 缩放比例,默认为1
:return: 返回旋转后的图像
"""
h, w = img.shape[:2] # 获取图像高、宽
# 旋转中心默认为图像中心
if center is None:
center = (w / 2, h / 2)
# 计算旋转矩阵
M = cv2.getRotationMatrix2D(center, angle, scale) #默认逆时针旋转
# 使用openCV仿射变换实现函数旋转
rotated = cv2.warpAffine(img, M, (w, h))
return rotated # 返回旋转后的矩阵
img = cv2.imread('../data/lena.jpg', 1)[..., ::-1]
x_50 = translate(img, 50, 0)
y_50 = translate(img, 0, 50)
r_45 = rotate(img, 45)
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(x_50)
axes[2].imshow(y_50)
axes[3].imshow(r_45)
plt.show()
缩放
最邻近插值法:直接使用新的像素点(x’, y’)最近的整数坐标灰度值作为该点的值,该方法计算量小,但精确度不高,并且可能破坏图像中的线性关系
双线性插值法:使用新的像素点(x’,y’)最邻近的四个像素值进行插值计算,假设为(i,j),(i+1,j) (i,j+1),(i+1,j+1),则u=x’-i,v=y’-j.简言之是通过横向和纵向的数据预测应该填充什么样的数据进行图像放大
img = cv2.imread('../data/lena.jpg', 1)[..., ::-1]
h, w = img.shape[:2]
dst_size = (int(w / 2), int(h / 2)) # 缩放目标尺寸,宽高均为原来1/2 ,除完了是浮点数,所以要int
resized_min = cv2.resize(img, dst_size) # 执行缩放
dst_size = (200, 300) # 缩放目标尺寸,宽200,高300
method = cv2.INTER_NEAREST # 最邻近插值,失真严重
resized_INTER_NEAREST = cv2.resize(img, dst_size, interpolation=method) # 执行缩放
dst_size = (200, 300) # 缩放目标尺寸,宽200,高300
method = cv2.INTER_LINEAR # 双线性插值
resized_INTER_LINEAR = cv2.resize(img, dst_size, interpolation=method) # 执行缩放
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(resized_min)
axes[2].imshow(resized_INTER_NEAREST)
axes[3].imshow(resized_INTER_LINEAR)
plt.show()
图像的裁剪
# 图像随机裁剪
def random_crop(im, w, h):
start_x = np.random.randint(0, im.shape[1]-w) # 裁剪起始x像素,-w避免切过了,超过图片本身大小
start_y = np.random.randint(0, im.shape[0]-h) # 裁剪起始y像素
new_img = im[start_y:start_y + h, start_x: start_x + w] # 执行裁剪
return new_img
# 图像中心裁剪
def center_crop(im, w, h):
start_x = int(im.shape[1] / 2) - int(w / 2) # 裁剪起始x像素
start_y = int(im.shape[0] / 2) - int(h / 2) # 裁剪起始y像素
new_img = im[start_y:start_y + h, start_x: start_x + w] # 执行裁剪
return new_img
img = cv2.imread('../data/banana_1.png', 1)[..., ::-1]
res1 = random_crop(img, 200, 200)
res2 = center_crop(img, 200, 200)
fig, axes = plt.subplots(1, 3, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(res1)
axes[2].imshow(res2)
plt.show()
图像相加
lena = cv2.imread('../data/lena.jpg', 0)[..., ::-1]
lily = cv2.imread('../data/lily_square.png', 0)[..., ::-1]
dst1 = cv2.add(lena, lily) # 图像直接相加,会导致图像过亮、过白
# 加权求和:addWeighted
# 图像进行加权和计算时,要求src1和src2必须大小、类型相同
dst2 = cv2.addWeighted(lena, 0.8, lily, 0.2, 0) # 最后一个参数为亮度调节量 ,这里是可以设置图像的权重的
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(lena, cmap=plt.get_cmap('gray'))
axes[1].imshow(lily, cmap=plt.get_cmap('gray'))
axes[2].imshow(dst1, cmap=plt.get_cmap('gray'))
axes[3].imshow(dst2, cmap=plt.get_cmap('gray'))
plt.show()
图像相减
a = cv2.imread("../data/3.png", 0) #[..., ::-1]
b = cv2.imread("../data/4.png", 0) #[..., ::-1]
dst = cv2.subtract(a, b) # 两幅图像相减,是求出图像的差异
fig, axes = plt.subplots(1, 3, figsize=(12, 12))
axes[0].imshow(a, cmap=plt.get_cmap('gray'))
axes[1].imshow(b, cmap=plt.get_cmap('gray'))
axes[2].imshow(dst, cmap=plt.get_cmap('gray'))
plt.show()
透视变换
img = cv2.imread('../data/pers.png', 1)[..., ::-1]
h, w = img.shape[:2]
src = np.float32([[58, 2], [167, 9], [8, 196], [126, 196]]) # 输入图像四个顶点坐标
dst = np.float32([[16, 2], [167, 8], [8, 196], [169, 196]]) # 输出图像四个顶点坐标
# 生成透视变换矩阵
M = cv2.getPerspectiveTransform(src # 输入图像四个顶点坐标
, dst) # 输出图像四个顶点坐标
res = cv2.warpPerspective(img, M, (w, h))
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(res)
plt.show()
图像腐蚀
img = cv2.imread("../data/5.png", 1)[..., ::-1]
# 腐蚀
kernel = np.ones((3, 3), np.uint8) # 用于腐蚀计算的核
erosion = cv2.erode(img, # 原始图像
kernel, # 腐蚀核写(3,3)会自动构建腐蚀核
iterations=3) # 迭代次数
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(erosion)
plt.show()
开运算,先腐蚀再膨胀
# 读取原始图像
im1 = cv2.imread("../data/7.png", 1)[..., ::-1]
im2 = cv2.imread("../data/8.png", 1)[..., ::-1]
# 执行开运算
k = np.ones((5, 5), np.uint8)
r1 = cv2.morphologyEx(im1, cv2.MORPH_OPEN, k, iterations=3)
r2 = cv2.morphologyEx(im2, cv2.MORPH_OPEN, k, iterations=3)
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(im1)
axes[1].imshow(r1)
axes[2].imshow(im2)
axes[3].imshow(r2)
plt.show()
礼帽运算
礼帽运算是用原始图像减去其开运算图像的操作。礼帽运算能够获取图像的噪声信 息,或者得到比原始图像的边缘更亮的边缘信息。
图像膨胀
img = cv2.imread("../data/9.png", 1)[..., ::-1]
# 腐蚀
kernel = np.ones((3, 3), np.uint8) # 用于膨胀计算的核
erosion = cv2.dilate(img, # 原始图像
kernel, # 膨胀核
iterations=5) # 迭代次数
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(erosion)
plt.show()
闭运算,先膨胀再腐蚀
# 读取原始图像
im1 = cv2.imread("../data/9.png", 1)[..., ::-1]
im2 = cv2.imread("../data/10.png", 1)[..., ::-1]
# 执行开运算
k = np.ones((3, 3), np.uint8)
r1 = cv2.morphologyEx(im1, cv2.MORPH_CLOSE, k, iterations=3)
r2 = cv2.morphologyEx(im2, cv2.MORPH_CLOSE, k, iterations=3)
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(im1)
axes[1].imshow(r1)
axes[2].imshow(im2)
axes[3].imshow(r2)
plt.show()
黑帽运算
黑帽运算是用闭运算图像减去原始图像的操作。黑帽运算能够获取图像内部的小孔, 或前景色中的小黑点,或者得到比原始图像的边缘更暗的边缘部分。
形态学梯度
o = cv2.imread("../data/6.png")
k = np.ones((2, 2), np.uint8)
r = cv2.morphologyEx(o, cv2.MORPH_GRADIENT, k, iterations=2)
fig, axes = plt.subplots(1, 2, figsize=(12, 12))
axes[0].imshow(o)
axes[1].imshow(r)
plt.show()
图像梯度处理
色彩梯度
模糊
img = cv2.imread("../data/salt.jpg", 1)[..., ::-1]
#均值滤波
res1 = cv2.blur(img, (5, 5))## 中值滤波
# 高斯滤波
# 第三个参数为高斯核在X方向的标准差
res2 = cv2.GaussianBlur(img, (5, 5), 3)
# 调用medianBlur中值模糊
# 第二个参数为滤波模板的尺寸大小,必须是大于1的奇数,如3、5、7
res3 = cv2.medianBlur(img, 3)
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axes[0][0].imshow(img)
axes[0][1].imshow(res1)
axes[1][0].imshow(res2)
axes[1][1].imshow(res3)
plt.show()
锐化
img = cv2.imread("../data/lena.jpg", 0)
#锐化算子1
shappen1 = np.array([[-1, - 1, - 1],
[-1, 9, -1],
[-1, -1, -1]])
res1 = cv2.filter2D(img,
-1, #与原始通道数量保持一致
shappen1)
#锐化算子2
shappen2 = np.array([[0, - 1, 0],
[-1, 8, -1],
[0, 1, 0]]) / 6.0
res2 = cv2.filter2D(img,
-1, #与原始通道数量保持一致,#图像深度
shappen2)
fig, axes = plt.subplots(1, 3, figsize=(12, 12))
axes[0].imshow(img, cmap=plt.get_cmap('gray'))
axes[1].imshow(res1, cmap=plt.get_cmap('gray'))
axes[2].imshow(res2, cmap=plt.get_cmap('gray'))
plt.show()
边沿检测
img = cv2.imread("../data/lily.png", 1)[..., ::-1]
# # 水平方向滤波
# hsobel = cv.Sobel(im, cv.CV_64F, 1, 0, ksize=5)
# cv.imshow('H-Sobel', hsobel)
# # 垂直方向滤波
# vsobel = cv.Sobel(im, cv.CV_64F, 0, 1, ksize=5)
# cv.imshow('V-Sobel', vsobel)
# 两个方向滤波
# cv2.CV_64F: 输出图像深度,本来应该设置为-1,但如果设成-1,可能会发生计算错误
# 所以通常先设置为精度更高的CV_64F
sobel = cv2.Sobel(img,
cv2.CV_64F, #图像深度
dx=1,
dy=1,
ksize=11) #滤波器大小
# Laplacian滤波:对细节反映更明显
laplacian = cv2.Laplacian(img,
cv2.CV_64F,
)
# Canny边沿提取
canny = cv2.Canny(img
, 50 #滞后域值
, 50) #模糊度
fig, axes = plt.subplots(1, 4, figsize=(12, 12))
axes[0].imshow(img)
axes[1].imshow(sobel)
axes[2].imshow(laplacian)
axes[3].imshow(canny)
plt.show()