三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

OpenCV(图像的基本操作)

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('a+10', 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('a+b', 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 加权融合——addWeighted

cv2.addWeighted()函数可以实现两张图像的加权融合,公式为:dst = src1 * alpha + src2 * beta + gamma

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.addWeighted(a, 0.8, b, 0.2, 10) cv2.imshow('addWeighted', c) cv2.waitKey(0) cv2.destroyAllWindows()

这里alpha=0.8表示第一张图像占80%的权重,beta=0.2表示第二张图像占20%的权重,gamma=10是亮度调节参数。

二、图像的边界填充

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, n=10000): 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)

方框滤波与均值滤波类似,但可以选择是否归一化:

# normalize=True 时与均值滤波效果相同 boxFilter_1 = cv2.boxFilter(noise, -1, (3, 3), normalize=True) # normalize=False 时只求和,不取平均 boxFilter_2 = cv2.boxFilter(noise, -1, (3, 3), normalize=False)

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】:

  1. 任选一张图片作为背景图,并将其尺寸调整为 640×480。

  2. 将背景图转换为灰度图。

  3. 打开摄像头,逐帧完成以下操作:

    • 将当前摄像头帧转换为灰度图。

    • 使用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】:

  1. 使其每一帧画面都随机生成 8000 个黑白点(椒盐噪声)添加噪声效果。

  2. 然后选择合适的图像平滑处理方法,处理每一帧画面,消除噪声的同时尽可能使原画面清晰。

  3. 同时弹出三个窗口,分别展示原视频、含有噪声的视频、平滑处理后的视频。

7.2 代码实现

import cv2 import numpy as np def add_saltnoise(frame, n=8000): 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,在局部区域内排序后,这些极值会排在最前面或最后面。

  • 中值滤波取排序后的中间值,因此椒盐噪声不会影响中值的选取。

  • 相比均值滤波和高斯滤波,中值滤波能更好地保留图像的边缘和细节信息。

如果尝试使用均值滤波或高斯滤波,会发现图像虽然去除了部分噪声,但整体变得模糊,细节丢失严重。而中值滤波在去除椒盐噪声的同时,能较好地保持图像清晰度。

← 返回列表