文章目录
- 前言
- 一、几何变换
- 1.1 缩放
- 1.2 平移
- 1.3 旋转
- 1.4 翻转
- 1.5 仿射
- 1.6 透视
- 二、低通滤波
- 2.1 均值滤波
- 2.2 高斯滤波
- 2.3 中值滤波
- 2.4 双边滤波
- 2.5 自定义滤波
- 三、高通滤波
- 3.1 Sobel
- 3.2 Scharr
- 3.3 Laplacian
- 3.4 Canny
- 四、图像金字塔
- 4.1 高斯金字塔
- 4.2 拉普拉斯金字塔
- 五、形态学
- 5.1 腐蚀
- 5.2 膨胀
- 5.3 运算
- 六、直方图
- 6.1 计算
- 6.2 均衡
- 6.3 反向投影
- 七、轮廓
- 7.1 查找显示
- 7.2 常用特征
- 参考
前言
图像处理是计算机视觉领域中的核心技术之一,它涉及到对图像进行各种变换、滤波、金字塔构建、形态学操作等一系列处理。在本篇博文中,我们将深入探讨使用OpenCV和Python进行图像处理的各种技术和方法。从几何变换到滤波、金字塔构建再到形态学操作,我们将逐步介绍并实践这些重要的图像处理技术,帮助读者更好地理解和应用于实际项目中。
一、几何变换
1.1 缩放
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def generateCanvas(img, img2):
canvas = np.zeros((max(img.shape[0], img2.shape[0]),
img.shape[1] + img2.shape[1], 3), dtype=np.uint8)
# 将图像1和图像2放置在画布上
canvas[:img.shape[0], :img.shape[1]] = img
canvas[:img2.shape[0],
img.shape[1]:] = img2
return canvas
def main():
img = cv.imread('sudoku.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# dsize:输出图像的大小。如果这个参数不为None,那么就代表将原图像缩放到这个Size(width,height)指定的大小;
# 如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:
# dsize = Size(round(fx*src.cols), round(fy*src.rows))
# 其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。
# fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算;
# fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算;
# interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:
# INTER_NEAREST - 最近邻插值
# INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
# INTER_AREA - 使用像素区域关系进行重采样
# INTER_CUBIC - 4x4像素邻域内 的双立方插值
# INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
fx = 0.6
fy = 0.6
img_half_nearest = cv.resize(
img, dsize=None, fx=fx, fy=fy, interpolation=cv.INTER_NEAREST)
img_half_linear = cv.resize(
img, dsize=None, fx=fx, fy=fy, interpolation=cv.INTER_LINEAR)
img_half_area = cv.resize(img, dsize=None, fx=fx,
fy=fy, interpolation=cv.INTER_AREA)
img_half_cubic = cv.resize(
img, dsize=None, fx=fx, fy=fy, interpolation=cv.INTER_CUBIC)
img_half_lanczos4 = cv.resize(
img, dsize=None, fx=fx, fy=fy, interpolation=cv.INTER_LANCZOS4)
titles = ['nearest', 'linear', 'area', 'cubic', 'Lanczos']
images = [img_half_nearest, img_half_linear,
img_half_area, img_half_cubic, img_half_lanczos4]
# canvas = generateCanvas(img, img_half_linear)
# cv.imshow('test', canvas)
for i in range(len(images)):
show = cv.cvtColor(generateCanvas(img, images[i]), cv.COLOR_BGR2RGB)
plt.subplot(
2, 3, i+1), plt.imshow(show)
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
1.2 平移
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
rows, cols, _ = img.shape
M = np.float32([[1, 0, 100], [0, 1, 50]])
# M: 2 * 3 矩阵
# 根据下面的公式,想实现平移,可以通过构造M=[[1, 0, x], [0, 1, y]],实现向右平移x,向下平移y
# dst(x, y) = (M[0,0] * x + M[0, 1] * y + M[0, 2], M[1, 0] * x + M[1, 1] * y + M[1, 2])
img_right_down = cv.warpAffine(img, M, (cols, rows))
M = np.float32([[1, 0, -100], [0, 1, -50]])
img_left_up = cv.warpAffine(img, M, (cols, rows))
titles = ['origin', 'right down', 'left up']
images = [img, img_right_down, img_left_up]
for i in range(len(images)):
show = cv.cvtColor(images[i], cv.COLOR_BGR2RGB)
plt.subplot(
1, 3, i+1), plt.imshow(show)
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
1.3 旋转
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
rows, cols, _ = img.shape
M = cv.getRotationMatrix2D(center=(rows / 2, cols / 2), angle=-30, scale=1)
img_rotete_minus_30 = cv.warpAffine(img, M, (cols, rows))
M = cv.getRotationMatrix2D(center=(rows / 2, cols / 2), angle=30, scale=1)
img_rotete_30 = cv.warpAffine(img, M, (cols, rows))
titles = ['origin', 'rotate -30 degree', 'rotate 30 degree']
images = [img, img_rotete_minus_30, img_rotete_30]
for i in range(len(images)):
show = cv.cvtColor(images[i], cv.COLOR_BGR2RGB)
plt.subplot(
1, 3, i+1), plt.imshow(show)
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
1.4 翻转
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 竖直翻转
img_vertical = cv.flip(img, flipCode=1)
# 水平翻转
img_horizontal = cv.flip(img, flipCode=0)
# 两者
img_both = cv.flip(img, flipCode=-1)
title = ['Origin', 'flipCode=1,Vertical',
'flipCode=0,Horizontal', 'flipCode=-1,Both']
# 对应的图像
imgs = [img, img_vertical, img_horizontal, img_both]
for i in range(len(imgs)):
plt.subplot(2, 2, i + 1)
plt.imshow(cv.cvtColor(imgs[i], cv.COLOR_BGR2RGB))
plt.title(title[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
1.5 仿射
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
rows, cols, _ = img.shape
# pts1 表示变换前三个点位置
# pts2 表示变换后三个点位置
pts1 = np.float32([[270, 270], [330, 270], [310, 320]])
pts2 = np.float32([[100, 100], [150, 50], [150, 100]])
M = cv.getAffineTransform(pts1, pts2)
img_result = cv.warpAffine(img, M, (cols, rows))
for p in pts1:
cv.circle(img, center=(int(p[0]), int(p[1])), radius=3,
color=(0, 0, 255), thickness=cv.FILLED)
for p in pts2:
cv.circle(img_result, center=(int(p[0]), int(p[1])), radius=3,
color=(0, 255, 0), thickness=cv.FILLED)
title = ['Origin', 'Affine']
# 对应的图像
imgs = [img, img_result]
for i in range(len(imgs)):
plt.subplot(1, len(imgs), i + 1)
plt.imshow(cv.cvtColor(imgs[i], cv.COLOR_BGR2RGB))
plt.title(title[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
1.6 透视
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
rows, cols, _ = img.shape
# 将图像投影到一个新的视平面,需要四个点, 在这4个点中,有3个点不应共线
# 平面的4个点顺序为 左上 右上 左下 右下
pts1 = np.float32([[270, 270], [330, 270], [270, 350], [320, 350]])
pts2 = np.float32([[0, 0], [512, 0], [0, 512], [512, 512]])
M = cv.getPerspectiveTransform(pts1, pts2)
img_result = cv.warpPerspective(img, M, (cols, rows))
for p in pts1:
cv.circle(img, center=(int(p[0]), int(p[1])), radius=3,
color=(0, 0, 255), thickness=cv.FILLED)
for p in pts2:
cv.circle(img_result, center=(int(p[0]), int(p[1])), radius=3,
color=(0, 255, 0), thickness=cv.FILLED)
title = ['Origin', 'Perspective',]
# 对应的图像
imgs = [img, img_result]
for i in range(len(imgs)):
plt.subplot(1, len(imgs), i + 1)
plt.imshow(cv.cvtColor(imgs[i], cv.COLOR_BGR2RGB))
plt.title(title[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
二、低通滤波
与一维信号一样,图像也可以用各种低通滤波器(low-pass filters,LPF)、高通滤波器(high-pass filters,HPF)等进行过滤
- LPF 用于降低某些像素强度,可以用于平滑图像,保留图像中的低频成分,过滤高频成分,帮助去除噪点,模糊图像等
- HPF 用于增强某些像素强度,可以用于帮助寻找图像中的边缘
2.1 均值滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lenaNoise.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 均值滤波: 简单的平均卷积操作
result = cv.blur(img, ksize=(5, 5))
# 显示图像
titles = ['origin image', 'blur image']
images = [img, result]
for i in range(2):
plt.subplot(
1, 2, i+1), plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
2.2 高斯滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lenaNoise.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 高斯滤波:
# ksize: 高斯核大小,可以是0或正奇数,当为0从sigma计算
result_0 = cv.GaussianBlur(img, ksize=(5, 5), sigmaX=0)
# 显示图像
titles = ['origin image', 'gaussian blur']
images = [img, result_0]
for i in range(len(images)):
plt.subplot(
1, len(images), i+1), plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
2.3 中值滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lenaNoise.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 中值滤波:
result_0 = cv.medianBlur(img, ksize=3)
result_1 = cv.medianBlur(img, ksize=5)
result_2 = cv.medianBlur(img, ksize=7)
# 显示图像
titles = ['origin image', 'ksize=3', 'ksize=5', 'ksize=7']
images = [img, result_0, result_1, result_2]
for i in range(len(images)):
plt.subplot(
2, 2, i+1), plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
2.4 双边滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lenaNoise.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 双边滤波: 在计算像素值的同时会考虑距离和色差信息,从而可在消除噪声得同时保护边缘信息,可用于美颜
# d 每个像素邻域的直径,当<=0时,从sigmaSpace计算 注意:当 d > 5 非常慢,所以建议使用=5作为实时应用,或者使用=9作为需要重噪声过滤的离线应用
# sigmaColor 颜色标准方差,一般尽可能大,较大的值表示在颜色相近的区域内像素将会被更多地保留
# sigmaSpace 坐标空间标准方差(像素单位),一般尽可能小,较小的值表示在距离中心像素较远的像素的权重较小
result_0 = cv.bilateralFilter(img, d=0, sigmaColor=100, sigmaSpace=15)
result_1 = cv.bilateralFilter(img, d=0, sigmaColor=50, sigmaSpace=15)
# 显示图像
titles = ['origin image', 'color=100 space=5', 'color=50 space=5']
images = [img, result_0, result_1]
for i in range(len(images)):
plt.subplot(
1, len(images), i+1), plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
2.5 自定义滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def main():
img = cv.imread('lenaNoise.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 自定义滤波,对图像进行卷积运算:
# 先定义卷积核,再参与计算,ddepth=-1表示和源图像深度一致
# 此外还有个参数anchor,默认值为(-1,-1)表示锚点位于内核中心
kernel = np.ones((5, 5), np.float32)/25
result = cv.filter2D(img, ddepth=-1, kernel=kernel)
# 显示图像
titles = ['origin image', 'filter2d filter',]
images = [img, result]
for i in range(len(images)):
plt.subplot(
1, len(images), i+1), plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
三、高通滤波
3.1 Sobel
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def sobel(img):
# Sobel 使用Sobel算子计算一阶、二阶、三阶或三者混合 图像导数梯度
# ddepth 输出图像的深度,计算图像的梯度会有浮点数,负数,所以后面会取绝对值
# dx,dy X和Y方向的梯度
# ksize 参与图像卷积操作的核,大小可以是1、3、5或7,用于不同精度的边缘检测
grad_x = cv.Sobel(img, ddepth=cv.CV_64F, dx=1, dy=0, ksize=3)
abs_grad_x = cv.convertScaleAbs(grad_x)
grad_y = cv.Sobel(img, ddepth=cv.CV_64F, dx=0, dy=1, ksize=3)
abs_grad_y = cv.convertScaleAbs(grad_y)
# 分别计算x和y,再求和
sobel = cv.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
return abs_grad_x, abs_grad_y, sobel
def main():
img = cv.imread('res2.jpg')
img_lena = cv.imread('lena.png', cv.IMREAD_GRAYSCALE)
assert img is not None and img_lena is not None, "file could not be read, check with os.path.exists()"
# 显示图像
titles = ['origin ', 'sobel_x', 'sobel_y', 'sobel_x+sobel_y', 'lena ',
'lena_sobel_x', 'lena_sobel_y', 'lena_sobel_x+lena_sobel_y']
images = [img, *sobel(img), img_lena, *sobel(img_lena)]
for i in range(len(images)):
plt.subplot(2, 4, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
3.2 Scharr
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
scharrx = cv.Scharr(img, ddepth=cv.CV_64F, dx=1, dy=0)
scharrxAbs = cv.convertScaleAbs(scharrx)
scharry = cv.Scharr(img, ddepth=cv.CV_64F, dx=0, dy=1)
scharryAbs = cv.convertScaleAbs(scharry)
# 分别计算x和y,再求和
scharrxy = cv.addWeighted(scharrxAbs, 0.5, scharryAbs, 0.5, 0)
# 显示图像
titles = ['origin ', 'scharrx', 'scharry', 'scharrx + scharry']
images = [img, scharrxAbs, scharryAbs, scharrxy]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
3.3 Laplacian
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
result = cv.Laplacian(img, cv.CV_16S, ksize=3)
resultAbs = cv.convertScaleAbs(result)
# 显示图像
titles = ['origin ', 'Laplacian']
images = [img, resultAbs]
for i in range(len(images)):
plt.subplot(1, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
3.4 Canny
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# canny 是一个多步算法,先用5*5核高斯滤波过滤噪声,然后用Sobel算法查找图像梯度,最后去除任何可能不构成边缘的、不需要的像素
# threshold1 较小值 低于这个值的肯定不是边
# threshold2 较大值 高于这个值的肯定是边
# 位于两者之间的需要进行连通性判断
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_result_1 = cv.Canny(img_gray, threshold1=100, threshold2=200)
img_result_2 = cv.Canny(img_gray, threshold1=50, threshold2=240)
# 显示图像
titles = ['origin ', 'gray', 'canny 100,200', 'canny 50,240']
images = [img, img_gray, img_result_1, img_result_2]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
四、图像金字塔
通常,我们通常处理恒定大小的图像。但在某些情况下,我们需要处理不同分辨率的(相同的)图像。例如,当在图像中搜索某个东西时,比如人脸,我们不确定物体将在该图像中出现在什么大小。在这种情况下,我们将需要创建一组具有不同分辨率的相同图像,并在所有这些图像中搜索对象。这些不同分辨率的图像集被称为图像金字塔(因为当它们被保存在一个堆栈中,顶部是最高分辨率的图像时,它看起来就像一个金字塔)。
金字塔的一个应用是图像混合。
4.1 高斯金字塔
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 模糊图像向下采样
img_down = cv.pyrDown(img)
img_down_down = cv.pyrDown(img_down)
img_down_down_down = cv.pyrDown(img_down_down)
# 显示图像
titles = ['origin ', '向下采样1', '向下采样2', '向下采样3']
images = [img, img_down, img_down_down, img_down_down_down]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
4.2 拉普拉斯金字塔
拉普拉斯金字塔是由高斯金字塔形成的。拉普拉斯金字塔图像像边缘图像,它的大多数元素都是零,常被用于图像压缩。拉普拉斯金字塔的水平是由高斯金字塔的水平与高斯金字塔的扩展水平的差异形成的
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# pyrDown和pyrUp并不是可逆的
img_down = cv.pyrDown(img)
img_down_up = cv.pyrUp(img_down)
img_laplacian = img - img_down_up
# 显示图像
titles = ['origin ', 'img_laplacian']
images = [img, img_laplacian]
for i in range(len(images)):
plt.subplot(1, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
五、形态学
5.1 腐蚀
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('j.png')
assert img is not None, "file could not be read, check with os.path.exists()"
kernel3 = np.ones((3, 3), np.uint8)
img_erode_3 = cv.erode(img, kernel3, iterations=1)
kernel5 = np.ones((5, 5), np.uint8)
img_erode_5 = cv.erode(img, kernel5, iterations=1)
cross = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
img_erode_by_cross = cv.erode(img, cross)
ellipse = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
img_erode_by_ellipse = cv.erode(img, ellipse)
# 显示图像
titles = ['origin ', 'erode_3', 'erode_5', 'cross', 'ellipse']
images = [img, img_erode_3, img_erode_5,
img_erode_by_cross, img_erode_by_ellipse]
for i in range(len(images)):
plt.subplot(2, 3, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
5.2 膨胀
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('j.png')
assert img is not None, "file could not be read, check with os.path.exists()"
kernel3 = np.ones((3, 3), np.uint8)
img_dilate_3 = cv.dilate(img, kernel3, iterations=1)
kernel5 = np.ones((5, 5), np.uint8)
img_dilate_5 = cv.dilate(img, kernel5, iterations=1)
cross = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
img_dilate_by_cross = cv.dilate(img, cross)
ellipse = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
img_dilate_by_ellipse = cv.dilate(img, ellipse)
# 显示图像
titles = ['origin ', 'dilate_3', 'dilate_5', 'cross', 'ellipse']
images = [img, img_dilate_3, img_dilate_5,
img_dilate_by_cross, img_dilate_by_ellipse]
for i in range(len(images)):
plt.subplot(2, 3, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
5.3 运算
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('j.png')
assert img is not None, "file could not be read, check with os.path.exists()"
# 形态学通常用于二值化图像
# 开操作:先腐蚀后膨胀
# 闭操作:先膨胀后腐蚀
# 形态学梯度:膨胀减去腐蚀。
# 顶帽:原图像与开操作图像之间的差值图像。
# 黑帽:闭操作图像与原图像之间的差值图像。
kernel = np.ones((3, 3), np.uint8)
img_opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
img_closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
img_gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
img_tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
img_blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
# 显示图像
titles = ['origin ', 'open', 'close', 'gradient', 'tophat', 'blackhat']
images = [img, img_opening, img_closing,
img_gradient, img_tophat, img_blackhat]
for i in range(len(images)):
plt.subplot(2, 3, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
六、直方图
6.1 计算
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_hist = cv.calcHist([img], [0], None, [256], [0, 256])
# 显示图像
titles = ['gray', 'origin']
images = [img_gray, img]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.subplot(2, 2, 3)
plt.plot(img_hist)
plt.title('histogram')
plt.xlim([0, 256])
color = ('b', 'g', 'r')
plt.subplot(2, 2, 4)
for i, col in enumerate(color):
histr = cv.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.xlim([0, 256])
plt.title('color histogram')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
6.2 均衡
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_hist = cv.calcHist([img_gray], [0], None, [256], [0, 256])
# 均衡灰度图像
img_gray_equ = cv.equalizeHist(img_gray)
img_equ_hist = cv.calcHist([img_gray_equ], [0], None, [256], [0, 256])
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
img_clahe = clahe.apply(img_gray)
# 显示图像
titles = ['origin ', 'gray', 'equ', 'clahe']
images = [img, img_gray, img_gray_equ, img_clahe]
for i in range(len(images)):
plt.subplot(3, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.subplot(3, 2, 5)
plt.plot(img_hist)
plt.title('orgin hist')
plt.xlim([0, 256])
plt.subplot(3, 2, 6)
plt.plot(img_equ_hist)
plt.title('equ hist')
plt.xlim([0, 256])
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
6.3 反向投影
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('lena.png')
img_2 = cv.imread('lena.png')
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
img_hist = cv.calcHist([img_hsv], [0], None, [256], [0, 256])
cv.normalize(img_hist, img_hist, 0, 255, cv.NORM_MINMAX)
# 直方图反投影 将每个像素用概率表示 经常用于图像分割或在图像中查找感兴趣的对象, 和 camshift meanshift等算法一起使用
dst = cv.calcBackProject([img_hsv], [0, 1], img_hist, None, 1)
# 显示图像
titles = ['origin ', 'gray', 'backProject']
images = [img, img_gray, dst]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
七、轮廓
7.1 查找显示
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('box.jpg')
img_2 = img.copy()
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(img_gray, 127, 255, 0)
# 进行查找轮廓的图片应该是二值化图片,黑色背景中找白色物体轮廓
contours, hierarchy = cv.findContours(
thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img_2, contours, -1, (0, 255, 0), 3)
# cv.drawContours(img_2, contours, 3, (0, 255, 0), 3)
# cnt = contours[4]
# cv.drawContours(img_2, [cnt], 0, (0, 255, 0), 3)
# 显示图像
titles = ['origin ', 'binary', 'cornor']
images = [img, thresh, img_2]
for i in range(len(images)):
plt.subplot(2, 2, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
7.2 常用特征
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei']
def main():
img = cv.imread('g.png')
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(img_gray, 127, 255, 0)
# 进行查找轮廓的图片应该是二值化图片,黑色背景中找白色物体轮廓
contours, hierarchy = cv.findContours(
thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
M = cv.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
print('重心:', cx, cy)
area = cv.contourArea(cnt)
print('面积:', area)
perimeter = cv.arcLength(cnt, True)
print('周长:', perimeter)
# 轮廓近似图像
img_approx = img.copy()
epsilon = 0.01*cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
cv.drawContours(img_approx, [approx], -1, (0, 255, 0), 2)
# 外接矩形
img_rect = img.copy()
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(img_rect, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 最小矩形
img_min_rect = img.copy()
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img_min_rect, [box], 0, (0, 0, 255), 2)
# 外接圆
img_circle = img.copy()
(x, y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img_circle, center, radius, (0, 255, 0), 2)
# 凸包
img_hull = img.copy()
hull = cv.convexHull(cnt)
cv.drawContours(img_hull, [hull], 0, (0, 255, 0), 2)
# 椭圆
img_ellipse = img.copy()
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img_ellipse, ellipse, (0, 255, 0), 2)
# 线
img_line = img.copy()
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img_line, (cols-1, righty), (0, lefty), (0, 255, 0), 2)
cv.drawContours(img, contours, -1, (0, 255, 0), 2)
# 显示图像
titles = ['binary', 'cornor', 'approx',
'rect', 'min_rect', 'circle', 'hull', 'ellipse', 'line']
images = [thresh, img, img_approx, img_rect,
img_min_rect, img_circle, img_hull, img_ellipse, img_line]
for i in range(len(images)):
plt.subplot(3, 3, i+1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
参考
- https://github.com/LeBron-Jian/ComputerVisionPractice