OpenCV(图像的基本操作)
一、图像的基本操作1.1 图像的读取与显示在 OpenCV 中我们使用cv2.imread()函数来读取图片该函数接受图片路径作为参数返回一个 NumPy 数组表示的图像数据。使用cv2.imshow()函数来展示图像cv2.waitKey()控制窗口的显示时间cv2.destroyAllWindows()用于关闭所有窗口。import cv2 # 读取图像 img cv2.imread(bread.png) # 显示图像 cv2.imshow(bread, img) # 等待按键0表示无限等待 cv2.waitKey(0) # 关闭所有窗口 cv2.destroyAllWindows()1.2 图像的基本属性读取后的图像是一个 NumPy 数组我们可以通过以下方式获取图像的基本信息print(img.shape) # 图像的高度、宽度和通道数 print(img.dtype) # 数组的数据类型 print(img.size) # 图像的总像素数1.3 像素值的算术运算图像的像素值可以进行各种算术运算。在下面的例子中我们将一张图像的所有像素值统一增加10观察图像的变化import cv2 a cv2.imread(qie.png) c a 10 cv2.imshow(yuantu, a) cv2.imshow(a10, c) cv2.waitKey(0) cv2.destroyAllWindows()运行后可以发现所有像素值增加10后图像整体变亮了。这是因为像素值增大意味着颜色变亮。1.4 两张图像的相加我们还可以将两张图像对应位置的像素值相加。但需要注意的是两张图像必须具有相同的尺寸import cv2 a cv2.imread(qie.png) b cv2.imread(bread.png) # 截取相同大小的区域进行相加 d a[50:450, 50:400] b[50:450, 50:400] cv2.imshow(ab, d) cv2.waitKey(0) cv2.destroyAllWindows()如果两张图像的尺寸不一致需要先使用cv2.resize()将它们调整为相同尺寸import cv2 a cv2.imread(qie.png) b cv2.imread(bread.png) a cv2.resize(a, (400, 400)) b cv2.resize(b, (400, 400)) c cv2.add(a, b) cv2.imshow(a add b, c) cv2.waitKey(0) cv2.destroyAllWindows()cv2.add()与直接使用运算符的区别在于cv2.add()会对结果进行截断处理确保像素值不超过255而直接使用运算符则可能产生溢出。1.5 加权融合——addWeightedcv2.addWeighted()函数可以实现两张图像的加权融合公式为dst src1 * alpha src2 * beta gammaimport cv2 a cv2.imread(qie.png) b cv2.imread(bread.png) a cv2.resize(a, (400, 400)) b cv2.resize(b, (400, 400)) c cv2.addWeighted(a, 0.8, b, 0.2, 10) cv2.imshow(addWeighted, c) cv2.waitKey(0) cv2.destroyAllWindows()这里alpha0.8表示第一张图像占80%的权重beta0.2表示第二张图像占20%的权重gamma10是亮度调节参数。二、图像的边界填充cv2.copyMakeBorder()函数用于为图像添加边界填充。OpenCV 提供了多种填充方式import cv2 bread cv2.imread(view.png) bread cv2.resize(bread, dsize(400, 400)) top, bottom, left, right 50, 50, 50, 50 # 常数填充用指定的颜色填充边界 constant cv2.copyMakeBorder(bread, top, bottom, left, right, cv2.BORDER_CONSTANT, value[220, 25, 80]) # 反射填充以边界为对称轴进行反射 reflect cv2.copyMakeBorder(bread, top, bottom, left, right, cv2.BORDER_REFLECT) # 反射填充不含边界 reflect101 cv2.copyMakeBorder(bread, top, bottom, left, right, cv2.BORDER_REFLECT101) # 复制填充复制最边缘的像素 replicate cv2.copyMakeBorder(bread, top, bottom, left, right, cv2.BORDER_REPLICATE) # 包装填充将图像像包装纸一样循环 wrap cv2.copyMakeBorder(bread, top, bottom, left, right, cv2.BORDER_WRAP) cv2.imshow(bread, bread) cv2.imshow(constant, constant) cv2.imshow(reflect, reflect) cv2.imshow(reflect101, reflect101) cv2.imshow(replicate, replicate) cv2.imshow(wrap, wrap) cv2.waitKey(0) cv2.destroyAllWindows()各种填充方式的区别BORDER_CONSTANT用指定的颜色值填充边界。BORDER_REFLECT以边界为对称轴进行反射填充如gfedcba|abcdefg|gfedcba。BORDER_REFLECT101以边界为对称轴进行反射填充不含边界如gfedcb|abcdefg|gfedcba。BORDER_REPLICATE复制最边缘的像素值进行填充如aaaaaa|abcdefg|ggggggg。BORDER_WRAP将图像像包装纸一样循环填充如cdefgh|abcdefg|abcdefg。三、图像阈值处理阈值处理是图像处理中非常基础且重要的操作它可以将图像转换为二值图像或进行其他形式的像素值变换import cv2 view cv2.imread(view.png, 0) # 以灰度模式读取 view cv2.resize(view, dsize(400, 400)) # 五种阈值处理方式 ret0, binary cv2.threshold(view, 175, 255, cv2.THRESH_BINARY) ret1, binaryinv cv2.threshold(view, 175, 255, cv2.THRESH_BINARY_INV) ret2, trunc cv2.threshold(view, 175, 255, cv2.THRESH_TRUNC) ret3, tozero cv2.threshold(view, 175, 255, cv2.THRESH_TOZERO) ret4, tozeroinv cv2.threshold(view, 175, 255, cv2.THRESH_TOZERO_INV) cv2.imshow(view_gray, view) cv2.imshow(binary, binary) cv2.imshow(binaryinv, binaryinv) cv2.imshow(trunc, trunc) cv2.imshow(tozero, tozero) cv2.imshow(tozeroinv, tozeroinv) cv2.waitKey(0) cv2.destroyAllWindows()五种阈值方式的含义THRESH_BINARY大于阈值的像素设为255小于等于阈值的设为0。THRESH_BINARY_INV与 BINARY 相反大于阈值的设为0小于等于阈值的设为255。THRESH_TRUNC大于阈值的像素设为阈值小于等于阈值的不变。THRESH_TOZERO小于阈值的像素设为0大于等于阈值的不变。THRESH_TOZERO_INV与 TOZERO 相反大于阈值的设为0小于等于阈值的不变。四、视频的读取与处理视频的读取与图片的读取类似只不过视频是由多张图片帧组成的。读取视频时我们需要将视频中的每一帧都读取到然后通过cv2.waitKey()来调节每一帧的显示时间从而控制视频的播放速度。4.1 从摄像头读取视频import cv2 # 打开摄像头0表示默认摄像头 video cv2.VideoCapture(0) while True: ret, frame video.read() # 读取一帧 if not ret: break cv2.imshow(frame, frame) if cv2.waitKey(100) 27: # 按下ESC键退出 break video.release() cv2.destroyAllWindows()4.2 摄像头实时阈值处理我们可以将摄像头捕获的每一帧进行实时阈值处理import cv2 view cv2.VideoCapture(0) while True: ret, frame view.read() if not ret: break # 转换为灰度图 frame cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 多种阈值处理 ret0, binary cv2.threshold(frame, 175, 255, cv2.THRESH_BINARY) ret1, binaryinv cv2.threshold(frame, 175, 255, cv2.THRESH_BINARY_INV) ret2, trunc cv2.threshold(frame, 175, 255, cv2.THRESH_TRUNC) ret3, tozero cv2.threshold(frame, 175, 255, cv2.THRESH_TOZERO) ret4, tozeroinv cv2.threshold(frame, 175, 255, cv2.THRESH_TOZERO_INV) cv2.imshow(gray, frame) cv2.imshow(binary, binary) cv2.imshow(binaryinv, binaryinv) cv2.imshow(tozero, tozero) cv2.imshow(tozeroinv, tozeroinv) if cv2.waitKey(100) 27: # ESC键退出 break view.release() cv2.destroyAllWindows()五、图像滤波——去除噪声图像在采集或传输过程中往往会引入噪声椒盐噪声黑白点噪声是最常见的一种。图像滤波的目的就是在保留图像细节的同时去除噪声。5.1 添加椒盐噪声首先我们编写一个函数来为图像添加椒盐噪声import cv2 import numpy as np def add_peppersalt_noise(img, n10000): result img.copy() h, w img.shape[:2] for i in range(n): x np.random.randint(0, h) y np.random.randint(0, w) if np.random.randint(0, 2) 0: result[x, y] 0 # 黑色椒盐 else: result[x, y] 255 # 白色椒盐 return result img cv2.imread(view.png) img cv2.resize(img, (500, 500)) cv2.imshow(view, img) noise add_peppersalt_noise(img) cv2.imshow(noise, noise) cv2.waitKey(0)5.2 均值滤波blur均值滤波是用卷积核内所有像素的平均值来代替中心像素值blur_1 cv2.blur(noise, (5, 5)) blur_2 cv2.blur(noise, (10, 10)) blur_3 cv2.blur(noise, (15, 15)) blur_4 cv2.blur(noise, (60, 60)) cv2.imshow(blur_1, blur_1) cv2.imshow(blur_2, blur_2) cv2.imshow(blur_3, blur_3) cv2.imshow(blur_4, blur_4)卷积核越大去噪效果越强但图像也会变得越模糊。因此需要在去噪和保留细节之间取得平衡。5.3 方框滤波boxFilter方框滤波与均值滤波类似但可以选择是否归一化# normalizeTrue 时与均值滤波效果相同 boxFilter_1 cv2.boxFilter(noise, -1, (3, 3), normalizeTrue) # normalizeFalse 时只求和不取平均 boxFilter_2 cv2.boxFilter(noise, -1, (3, 3), normalizeFalse)5.4 高斯滤波GaussianBlur高斯滤波使用高斯核进行卷积对高斯噪声有很好的去噪效果GaussianB cv2.GaussianBlur(noise, (3, 3), 1) cv2.imshow(GaussianB, GaussianB)5.5 中值滤波medianBlur中值滤波是用卷积核内所有像素的中值来代替中心像素值。中值滤波对椒盐噪声的去噪效果非常好因为椒盐噪声的像素值0或255在排序后通常位于两端不会影响中值medianB cv2.medianBlur(noise, 3) cv2.imshow(medianB, medianB)不同滤波方法的适用场景均值滤波适用于去除均匀噪声但会模糊边缘。高斯滤波适用于去除高斯噪声对边缘保留较好。中值滤波特别适用于去除椒盐噪声能很好地保留边缘信息。六、实战项目一摄像头画面与背景图融合6.1 项目需求编写 Python 代码实现摄像头实时画面与背景图片融合的完整功能要求如下【参考 design_question1.py】任选一张图片作为背景图并将其尺寸调整为 640×480。将背景图转换为灰度图。打开摄像头逐帧完成以下操作将当前摄像头帧转换为灰度图。使用cv2.addWeighted()实现两张图像的加权融合彩色摄像头帧 彩色背景图各占 50% 透明度灰度摄像头帧 灰度背景图各占 50% 透明度同时显示两个窗口彩色融合画面 灰度融合画面6.2 代码实现import cv2 # 读取并调整背景图 view cv2.imread(view.png) view cv2.resize(view, (640, 480)) view_gray cv2.cvtColor(view, cv2.COLOR_BGR2GRAY) # 打开摄像头 video cv2.VideoCapture(0) while True: ret, frame video.read() if not ret: break # 调整摄像头帧尺寸 frame cv2.resize(frame, (640, 480)) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 彩色融合各占50% set_color cv2.addWeighted(view, 0.5, frame, 0.5, 0) # 灰度融合各占50% set_gray cv2.addWeighted(view_gray, 0.5, gray, 0.5, 0) cv2.imshow(set_color, set_color) cv2.imshow(set_gray, set_gray) if cv2.waitKey(100) 27: # ESC键退出 break video.release() cv2.destroyAllWindows()七、实战项目二视频去噪处理7.1 项目需求给定一个视频文件test.avi要求完成以下操作【参考 design_question2.py】使其每一帧画面都随机生成 8000 个黑白点椒盐噪声添加噪声效果。然后选择合适的图像平滑处理方法处理每一帧画面消除噪声的同时尽可能使原画面清晰。同时弹出三个窗口分别展示原视频、含有噪声的视频、平滑处理后的视频。7.2 代码实现import cv2 import numpy as np def add_saltnoise(frame, n8000): result frame.copy() h, w frame.shape[:2] for i in range(n): x np.random.randint(0, h) y np.random.randint(0, w) if np.random.randint(0, 2) 0: result[x, y] 0 else: result[x, y] 255 return result video cv2.VideoCapture(test.avi) while True: ret, frame video.read() if not ret: break # 添加椒盐噪声 noise add_saltnoise(frame) # 中值滤波去噪对椒盐噪声效果最好 median cv2.medianBlur(noise, 3) cv2.imshow(frame, frame) # 原视频 cv2.imshow(noise, noise) # 含噪声的视频 cv2.imshow(median, median) # 平滑处理后的视频 if cv2.waitKey(100) 27: # ESC键退出 break video.release() cv2.destroyAllWindows()7.3 为什么选择中值滤波在本项目中我们添加的是椒盐噪声随机出现的黑白点。对于这种噪声中值滤波是最佳选择。原因如下椒盐噪声的像素值为 0 或 255在局部区域内排序后这些极值会排在最前面或最后面。中值滤波取排序后的中间值因此椒盐噪声不会影响中值的选取。相比均值滤波和高斯滤波中值滤波能更好地保留图像的边缘和细节信息。如果尝试使用均值滤波或高斯滤波会发现图像虽然去除了部分噪声但整体变得模糊细节丢失严重。而中值滤波在去除椒盐噪声的同时能较好地保持图像清晰度。