初始、图层创建zeros_like/zeros
1、zeros_like
np.zeros_like()
是 NumPy
库中的一个函数,它会根据给定的数组(如 img
)创建一个与之 形状和数据类型完全相同 的空白画布,并将所有元素初始化为 0。
img_contours = np.zeros_like(img)
2、zeros
np.zeros()
是 NumPy 库中的一个函数,用于创建 全零数组。- shape
- 当输入 单个整数 时,生成一维数组。
- 当输入 元组 时,生成多维数组。
表示数组的维度和大小:
- dtype
- 指定数组中元素的数据类型。例如:
float
(默认):浮点数类型。int
:整数类型。uint8
:无符号 8 位整数(0-255,常用于图像)。bool
:布尔值类型(True
或False
)。
- order
- 'C'(行优先):数据在内存中按行存储。
- 'F'(列优先):数据在内存中按列存储。
# 创建一个包含5个元素的全零数组 arr = np.zeros(5) # 创建一个 3x4 的二维全零数组 arr = np.zeros((3, 4), dtype=int) # 创建一个 3x3 全黑图像 (uint8 类型) black_image = np.zeros((3, 3), dtype=np.uint8) # 创建一个 300x300 的黑色RGB图像 black_rgb_image = np.zeros((300, 300, 3), dtype=np.uint8)
一、图像读取和保存
1、读取图片
# 读取图片 img = cv2.imread("img/cropped.jpg") # 读取方式 cv.IMREAD_COLOR: 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。 cv.IMREAD_GRAYSCALE:以灰度模式加载图像 cv.IMREAD_UNCHANGED:加载图像,包括alpha通道 # imread第二参数1,0,-1 img = cv2.imread("img/cropped.jpg", 1)
2、显示图片
# 基于cv2 cv.imshow('image',img) # 第一个参数是窗口名称-字符串。第二个参数是显示的对象 cv.waitKey(0) # 无限循环,触发按钮关闭弹窗 cv.destroyAllWindows() # 关闭所有的窗口;cv.destroyWindows(“。。”)关闭指定窗口
# 修改显示大小 # 创建图像和一个窗口 img = cv.imread('img/img.jpg') cv.namedWindow('image', 0) cv.resizeWindow("image", 600, 500)
PS:如果使用的是64位计算机,则必须k = cv.waitKey(0)按如下所示修改行:k = cv.waitKey(0) & 0xFF # 64位计算机输入判断 if cv2.waitKey(1) & 0xFF == ord('q'):
# 基于PLT plt.imshow(img, cmap = 'gray', interpolation = 'bicubic') plt.xticks([]), plt.yticks([]) # 隐藏 x 轴和 y 轴上的刻度值 plt.show()
3、保存图像
cv.imwrite('messigray.png',img) # 保存图像 示例: import numpy as np import cv2 as cv img = cv.imread('messi5.jpg',0) cv.imshow('image',img) k = cv.waitKey(0) if k == 27: # 等待ESC退出 cv.destroyAllWindows() elif k == ord('s'): # 等待关键字,保存和退出 cv.imwrite('messigray.png',img) cv.destroyAllWindows()
注意点:
(1)纯色图层的创建
# 创建一个黑色的图像,一个窗口 img = np.zeros((300, 512, 3), np.uint8) cv.namedWindow('image')
(2)灰度单通道转为彩色三通道
img2 = cv2.imread('img/img.jpg', cv2.IMREAD_GRAYSCALE) img2_color = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
二、视频读取、保存、暂停
1、读取视频
# 打开视频文件或摄像头 (0 表示默认摄像头) cap = cv2.VideoCapture("video.mp4") # 或 cv2.VideoCapture(0)
# 示例 import cv2 cap = cv2.VideoCapture("video.mp4") # 或 cv2.VideoCapture(0) # 检查是否成功打开 if not cap.isOpened(): print("Error: Cannot open video.") exit() # 循环读取视频帧 while True: ret, frame = cap.read() # ret 表示是否成功读取,frame 是帧数据 if not ret: # 读取失败或视频结束 print("Video has ended or cannot be read.") break # 显示帧 cv2.imshow("Video", frame) # 在这里处理每一帧图片 # 按 'q' 键退出 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()
2、get、set用法
# get方法 cap = cv2.VideoCapture("video/test.mp4") print(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 具体用法 cv2.CAP_PROP_POS_MSEC:当前视频帧的时间戳(毫秒) cv2.CAP_PROP_POS_FRAMES:当前视频帧的索引 cv2.CAP_PROP_POS_AVI_RATIO:视频文件相对位置 cv2.CAP_PROP_FRAME_WIDTH:帧的宽度 cv2.CAP_PROP_FRAME_HEIGHT:帧的高度 cv2.CAP_PROP_FPS:帧率 cv2.CAP_PROP_FOURCC:视频编解码器的四字符代码 cv2.CAP_PROP_FRAME_COUNT:视频帧数
# set方法 #图像宽度 image.set(3,600) image.set(cv.CAP_PROP_FRAME_WIDTH,600) #图像高度 image.set(4,500) image.set(cv.CAP_PROP_FRAME_HEIGHT,500) #视频帧率 image.set(5, 30) #设置帧率 image.set(cv.CAP_PROP_FPS, 30) #解码方式四字符 image.set(cv.CAP_PROP_FOURCC, cv.VideoWriter.fourcc('M', 'J', 'P', 'G')) #图像亮度 image.set(cv.CAP_PROP_BRIGHTNESS, 63) #设置亮度 -64 - 64 0.0 #图像对比度 image.set(cv.CAP_PROP_CONTRAST, 0) #设置对比度 -64 - 64 2.0 #图像曝光度 image.set(cv.CAP_PROP_EXPOSURE, 2000) #设置曝光值 1.0 - 5000 156.0
3、保存视频
重点:
(1)
gray_frame
写入 VideoWriter
时,使用的是灰度图像(单通道),而 VideoWriter
默认期望的是彩色图像(三通道),需要写成:out.write(cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2BGR))
(2)确保写入的每一帧大小和初始化的分辨率一致
(3)20.0表示每一秒帧数
import cv2 from matplotlib import pyplot as plt # 打开视频文件或摄像头 (0 表示默认摄像头) cap = cv2.VideoCapture("video/test.mp4") # 或 cv2.VideoCapture(0) frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter('video/output.avi', fourcc, 20.0, (frame_width, frame_height)) # 循环读取视频帧 while True: # read会读取下一帧 ret, frame = cap.read() # ret 表示是否成功读取,frame 是帧数据 if not ret: # 读取失败或视频结束 print("Video has ended or cannot be read.") break # 处理成灰度 gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 显示处理后的帧 cv2.imshow("Gray Video", gray_frame) out.write(cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2BGR)) # 这里的灰度处理只能这样写,不然会报错 # 按 'q' 键退出 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() out.release() cv2.destroyAllWindows()
4、暂停视频
import cv2 from matplotlib import pyplot as plt # 打开视频文件或摄像头 (0 表示默认摄像头) cap = cv2.VideoCapture("video/test.mp4") # 或 cv2.VideoCapture(0) paused = False # 循环读取视频帧 while True: if not paused: ret, frame = cap.read() # ret 表示是否成功读取,frame 是帧数据 if not ret: # 读取失败或视频结束 print("Video has ended or cannot be read.") break # 显示处理后的帧 cv2.imshow("Gray Video", frame) # 这样写的反应快一些 key = cv2.waitKey(20) & 0xFF if key == ord(' '): # 按空格键切换播放/暂停状态 paused = not paused elif key == 27: # 按 ESC 键退出 break # 释放资源 cap.release() cv2.destroyAllWindows()
三、图像上绘制图形
1、线段
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制一条线段 start_point = (100, 100) # 左上角 end_point = (400, 400) # 右下角 color = (0, 0, 255) # 红色线段 thickness = 3 # 粗细 cv2.line(img, start_point, end_point, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
2、矩形
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制一个矩形 top_left = (200, 200) bottom_right = (400, 400) color = (0, 255, 0) # 绿色矩形 thickness = 2 cv2.rectangle(img, top_left, bottom_right, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
3、圆形
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制一个圆形 center = (300, 300) # 中心 radius = 100 # 半径 color = (255, 0, 0) # 蓝色圆形img thickness = -1 # 填充圆形 cv2.circle(img, center, radius, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
4、椭圆
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制一个椭圆 center = (250, 250) # 中心点 axes = (150, 100) # 长轴150,短轴100 angle = 0 # 椭圆旋转角度 start_angle = 0 # 圆弧起始角度 end_angle = 360 # 圆弧终止高度 color = (0, 255, 255) # 黄色椭圆 thickness = 2 cv2.ellipse(img, center, axes, angle, start_angle, end_angle, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
5、多边形
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制一个多边形 points = np.array([[100, 100], [200, 50], [300, 150], [250, 200]], np.int32) points = points.reshape((-1, 1, 2)) # 表示四个顶点,每个顶点一行二列的数组 color = (255, 255, 0) # 青色 thickness = 2 # 第二个参数接收的数据中可以理解为:顶点,x坐标,y坐标 # 第三个参数为False,将获得一条连接所有点的折线 cv2.polylines(img, [points], True, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
6、文本
import cv2 from matplotlib import pyplot as plt import numpy as np img = np.zeros((512, 512, 3), np.uint8) # 生成黑色画布/帧对象 # 在画布上绘制文本 text = 'OpenCV' # 文本内容 position = (200, 250) # 文本位置 font = cv2.FONT_HERSHEY_SIMPLEX font_scale = 1.5 # 文本大小 color = (255, 255, 255) # 白色文本 thickness = 2 cv2.putText(img, text, position, font, font_scale, color, thickness) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
四、图像、视频绑定鼠标事件
1、应用(双击新增圆形)
import numpy as np import cv2 as cv # 鼠标回调函数 def draw_circle(event, x, y, flags, param): if event == cv.EVENT_LBUTTONDBLCLK: cv.circle(img, (x, y), 100, (255, 0, 0), -1) # 创建一个黑色的图像,一个窗口,并绑定到窗口的功能 img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while True: cv.imshow('image', img) if cv.waitKey(20) & 0xFF == 27: break cv.destroyAllWindows()
(1)setMouseCallback需要绑定一个窗口
(2)回调函数默认五个参数:事件类型、x坐标、y坐标、标志位、额外参数
2、鼠标事件
cv2.EVENT_FLAG_ALTKEY # Alt按钮 cv2.EVENT_FLAG_CTRLKEY # Ctrl按钮 cv2.EVENT_FLAG_LBUTTON # 左键单击 cv2.EVENT_FLAG_MBUTTON # 中键单击 cv2.EVENT_FLAG_RBUTTON # 右键单击 cv2.EVENT_FLAG_SHIFTKEY # Shift按钮 cv2.EVENT_LBUTTONDBLCLK # 左键双击 cv2.EVENT_RBUTTONDBLCLK # 右键双击 cv2.EVENT_LBUTTONDOWN # 长按左键 cv2.EVENT_RBUTTONDOWN # 长按右键 cv2.EVENT_LBUTTONUP # 松开左键 cv2.EVENT_RBUTTONUP # 松开右键 cv2.EVENT_MBUTTONDBLCLK # 中键双击 cv2.EVENT_MBUTTONDOWN # 按下中键 cv2.EVENT_MBUTTONUP # 松开中键 cv2.EVENT_MOUSEHWHEEL # 水平滚轮 cv2.EVENT_MOUSEWHEEL # 垂直滚轮 cv2.EVENT_MOUSEMOVE # 鼠标移动
3、其他用法/示例
(1)获取像素点的BGR值
关键点:img[,]传入的应该是y, x
import numpy as np import cv2 as cv # 鼠标回调函数 def draw_circle(event, x, y, flags, param): if event == cv.EVENT_LBUTTONDOWN: print(f'({x}, {y})') pixel_color = img[y, x] print("颜色值BGR:", pixel_color) # 创建图像和一个窗口 img = cv.imread('img/cropped.jpg') cv.namedWindow('image') # 绑定到窗口 cv.setMouseCallback('image', draw_circle) while True: # 这里会将img也传入回调函数中,在回调函数中可直接使用img变量 cv.imshow('image', img) if cv.waitKey(2) & 0xFF == 27: break cv.destroyAllWindows()
(2)鼠标拖动生成一个圆
关键点:刷新图像需要用copy原图,如果是纯色背景可以写成:
img = np.zeros((512, 512, 3), np.uint8) # 创建图层
img = np.zeros_like(img) # 清空图像,保留最新的直线
版本一:绘制后清空所有圆 import numpy as np import cv2 as cv center_x, center_y, r = 0, 0, 0 down_tag = False # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global center_x, center_y, r, down_tag, img_copy if event == cv.EVENT_LBUTTONDOWN: down_tag = True center_x = x center_y = y if event == cv.EVENT_MOUSEMOVE and down_tag: img_copy = img.copy() r = np.sqrt((x - center_x) ** 2 + (y - center_y) ** 2) start_point = (center_x, center_y) end_point = (x, y) cv.line(img_copy, start_point, end_point, (0, 255, 0), 1) if event == cv.EVENT_LBUTTONUP: down_tag = False cv.circle(img_copy, (center_x, center_y), int(r), (0, 0, 255), -1) img = cv.imread('img/cropped.jpg') img_copy = img.copy() cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while True: cv.imshow('image', img_copy) if cv.waitKey(2) & 0xFF == 27: break cv.destroyAllWindows() 版本二:保留绘制的圆 import numpy as np import cv2 as cv center_x, center_y, r = 0, 0, 0 down_tag = False cirle_data = [] # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global center_x, center_y, r, down_tag, img_copy, cirle_data if event == cv.EVENT_LBUTTONDOWN: down_tag = True center_x = x center_y = y if event == cv.EVENT_MOUSEMOVE and down_tag: img_copy = img.copy() if cirle_data: for i in cirle_data: cv.circle(img_copy, (i[0], i[1]), i[2], (0, 0, 255), 2) r = np.sqrt((x - center_x) ** 2 + (y - center_y) ** 2) start_point = (center_x, center_y) end_point = (x, y) cv.line(img_copy, start_point, end_point, (0, 255, 0), 1) if event == cv.EVENT_LBUTTONUP: down_tag = False img_copy = img.copy() if cirle_data: for i in cirle_data: cv.circle(img_copy, (i[0], i[1]), i[2], (0, 0, 255), 2) cirle_data.append((center_x, center_y, int(r))) cv.circle(img_copy, (center_x, center_y), int(r), (0, 0, 255), 2) img = cv.imread('img/cropped.jpg') img_copy = img.copy() cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while True: cv.imshow('image', img_copy) if cv.waitKey(2) & 0xFF == 27: break cv.destroyAllWindows()
(3)视频鼠标事件
import cv2 from matplotlib import pyplot as plt # 打开视频文件或摄像头 (0 表示默认摄像头) cap = cv2.VideoCapture("video/test.mp4") # 或 cv2.VideoCapture(0) paused = False def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_FLAG_LBUTTON: cv2.circle(frame, (x, y), 10, (0, 0, 255), -1) # 循环读取视频帧 while True: if not paused: ret, frame = cap.read() # ret 表示是否成功读取,frame 是帧数据 if not ret: # 读取失败或视频结束 print("Video has ended or cannot be read.") break # 显示处理后的帧 cv2.imshow("Gray Video", frame) cv2.setMouseCallback("Gray Video", draw_circle) key = cv2.waitKey(20) & 0xFF if key == ord(' '): # 按空格键切换播放/暂停状态 paused = not paused elif key == 27: # 按 ESC 键退出 break # 释放资源 cap.release() cv2.destroyAllWindows()
(4)操作完视频的某一帧后保存新视频
解决办法是:记录用户操作,每一次while循环都记录原视频帧数,新视频帧数,操作计数为列表,再重新抽帧形成新视频
import cv2 import numpy as np from videoDataClean import VideoDataClean # 打开视频文件或摄像头 (0 表示默认摄像头) cap = cv2.VideoCapture("video/test.mp4") # 或 cv2.VideoCapture(0) paused = False # 视频是否暂停 true--暂停状态 false--播放状态 fps_count = 0 # 某一帧操作计数 fps_index = 0 # 新视频当前帧序号 1为第一帧 # 获取视频宽高和帧率 frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) fourcc = cv2.VideoWriter_fourcc(*'XVID') out_path = "video/output.avi" out = cv2.VideoWriter(out_path, fourcc, fps, (frame_width, frame_height)) fps_list = [] # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global frame, fps_count # 使用当前帧 if event == cv2.EVENT_LBUTTONDOWN: # 左键单击绘制圆 fps_count += 1 cv2.circle(frame, (x, y), 30, (0, 0, 255), -1) # 循环读取视频帧 while True: if not paused: # 播放状态读取下一帧并显示,暂停状态就一直显示上一帧画面 ret, frame = cap.read() fps_count = 0 # print(cap.get(cv2.CAP_PROP_POS_FRAMES)) if not ret: print("视频没有了~") break fps_index += 1 # 原视频帧数,新视频帧数,操作计数 fps_list.append((int(cap.get(cv2.CAP_PROP_POS_FRAMES)), fps_index, fps_count)) cv2.imshow("show", frame) cv2.setMouseCallback("show", draw_circle) out.write(frame) # 键盘控制 key = cv2.waitKey(20) & 0xFF if key == ord(' '): # 空格键暂停/播放切换 paused = not paused elif key == 27: # ESC 键退出 break # 释放资源 cap.release() out.release() cv2.destroyAllWindows() video = VideoDataClean(fps_list, out_path) video.video_to_new()
import pandas as pd import cv2 import os class VideoDataClean: def __init__(self, input_data, video_path): self.data = input_data self.video_path = video_path def video_to_new(self): data = pd.DataFrame(self.data, columns=["A", "B", "C"]) # 获取每组最大帧号 clean_data = data.groupby("A")["B"].max() # 创建输出目录 # output_dir = "video/clean" # os.makedirs(output_dir, exist_ok=True) # 处理视频帧 cap = cv2.VideoCapture(self.video_path) if not cap.isOpened(): print(f"无法打开视频文件: {self.video_path}") return 0 # 获取视频属性 frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) fourcc = cv2.VideoWriter_fourcc(*'XVID') new_out_path = "video/clean/output.avi" out = cv2.VideoWriter(new_out_path, fourcc, fps, (frame_width, frame_height)) # 遍历帧并保存 for i, frame_idx in enumerate(clean_data - 1): cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) ret, frame = cap.read() if ret: print(f"处理原始帧{i},实际帧 {frame_idx}中") out.write(frame) else: print(f"原始帧{i},实际帧 {frame_idx}读取失败!") # 释放资源 cap.release() out.release() print(f"视频已保存至:{new_out_path}") # # 输入数据 # a = [(1, 1, 0), (2, 2, 0), (3, 3, 0), (4, 4, 0), (5, 5, 0), (6, 6, 0), (7, 7, 0), (8, 8, 0), (9, 9, 0), (10, 10, 0), (11, 11, 0), (12, 12, 0), (12, 13, 0), (12, 14, 0), (12, 15, 0), (12, 16, 0), (12, 17, 0), (12, 18, 0), (12, 19, 0), (12, 20, 0), (12, 21, 0), (12, 22, 0), (12, 23, 0), (12, 24, 1), (12, 25, 1), (12, 26, 1), (12, 27, 1), (12, 28, 1), (12, 29, 1), (12, 30, 1), (12, 31, 1), (12, 32, 1), (12, 33, 1), (12, 34, 2), (12, 35, 2), (12, 36, 2), (12, 37, 2), (12, 38, 2), (12, 39, 2), (12, 40, 2), (12, 41, 2), (12, 42, 2), (12, 43, 2), (12, 44, 2), (12, 45, 2), (12, 46, 3), (12, 47, 3), (12, 48, 3), (12, 49, 3), (12, 50, 3), (12, 51, 3), (12, 52, 3), (12, 53, 3), (12, 54, 3), (12, 55, 3), (12, 56, 3), (12, 57, 4), (12, 58, 4), (12, 59, 4), (12, 60, 4), (12, 61, 4), (12, 62, 4), (12, 63, 4), (12, 64, 4), (12, 65, 4), (12, 66, 4), (12, 67, 4), (12, 68, 4), (12, 69, 4), (12, 70, 4), (12, 71, 4), (12, 72, 4), (12, 73, 4), (12, 74, 4), (12, 75, 4), (12, 76, 4), (12, 77, 4), (12, 78, 4), (12, 79, 4), (12, 80, 4), (12, 81, 4), (12, 82, 4), (12, 83, 4), (12, 84, 4), (12, 85, 4), (12, 86, 4), (12, 87, 4), (12, 88, 4), (12, 89, 4), (12, 90, 4), (12, 91, 4), (12, 92, 4), (12, 93, 4), (12, 94, 4), (12, 95, 4), (12, 96, 4), (12, 97, 4), (12, 98, 4), (12, 99, 4), (12, 100, 4), (12, 101, 4), (12, 102, 4), (12, 103, 4), (12, 104, 4), (12, 105, 4), (12, 106, 5), (12, 107, 5), (12, 108, 5), (12, 109, 5), (12, 110, 5), (12, 111, 5), (12, 112, 5), (12, 113, 5), (12, 114, 5), (12, 115, 5), (12, 116, 5), (12, 117, 5), (12, 118, 5), (12, 119, 5), (12, 120, 5), (12, 121, 5), (12, 122, 5), (12, 123, 5), (12, 124, 5), (12, 125, 5), (12, 126, 5), (13, 127, 0), (14, 128, 0), (15, 129, 0), (16, 130, 0), (17, 131, 0), (18, 132, 0), (19, 133, 0), (20, 134, 0), (21, 135, 0), (22, 136, 0), (23, 137, 0), (24, 138, 0), (25, 139, 0), (26, 140, 0), (27, 141, 0), (28, 142, 0), (29, 143, 0), (30, 144, 0), (31, 145, 0), (32, 146, 0), (33, 147, 0), (34, 148, 0), (35, 149, 0), (36, 150, 0), (37, 151, 0)] # # video = VideoDataClean(a, "video/output.avi") # video.video_to_new()
五、滑动条
1、cv2.createTrackbarPos
# 创建一个滑动条,名称为R,绑定在窗口image,默认值0,最大值255,不调用函数 cv.createTrackbar('R','image',0,255,nothing)
2、cv2.getTrackbar
# 获取窗口image的滑动条r的值 r = cv.getTrackbarPos('R','image')
3、示例:调色板
import cv2 import numpy as np img = np.zeros((512,512,3), np.uint8) cv2.namedWindow("image") cv2.createTrackbar("R", 'image', 0, 255, lambda x: x) cv2.createTrackbar("G", 'image', 0, 255, lambda x: x) cv2.createTrackbar("B", 'image', 0, 255, lambda x: x) while True: r = cv2.getTrackbarPos("R", 'image') g = cv2.getTrackbarPos("G", 'image') b = cv2.getTrackbarPos("B", 'image') img[:] = [b, g, r] cv2.imshow("image", img) k = cv2.waitKey(1) & 0xFF if k == 27: break cv2.destroyAllWindows()
六、图像像素
1、获取像素值
# 根据坐标获取像素 # 输出的color为BGR值b,g,r img = cv.imread('img/img.jpg') color = img[y, x] # 这里要先传y,再传x # 仅访问蓝色像素 # 0表示通道的第一个数据 blue = img[100,100,0]
2、修改像素值
img[100,100] = [255, 155, 155]
3、其他方法
# 访问 RED 值 >>> img.item(10,10,2) 59 # 修改 RED 值 >>> img.itemset((10,10,2),100) >>> img.item(10,10,2) 100
七、图像属性访问
# 访问行、列、通道数 img.shape >>> (1920, 1440, 3) # 访问像素点总数 img.size >>> 8294400 # 访问图像数据类型 img.dtype >>> uint8 # 图像标签 # y轴取280~340行, x轴取330~390行 ball = img[280:340, 330:390] # 在某一个区域粘贴这一个ball, 必须大小一致 img[273:333, 100:160] = ball
八、通道拆分与隐藏
通道拆分
# 拆分rgb颜色通道 # 方法一 import cv2 # 读取彩色图像 img = cv2.imread('img/cropped.jpg') # 拆分通道 b, g, r = cv2.split(img) # 显示每个通道 cv2.imshow('Blue', b) cv2.imshow('Green', g) cv2.imshow('Red', r) cv2.waitKey(0) cv2.destroyAllWindows() # 方法二 # 获取蓝色通道 b = img [:, :, 0]
隐藏某一通道
# 隐藏红色通道 img [:, :, 2] = 0
九、图像边框
# 格式 # 参数:图像、上宽、下宽、左宽、右宽、边框类型、颜色 dst = cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value=无) # borderType常用类型 cv2.BORDER_CONSTANT:填充为常量颜色。 cv2.BORDER_REPLICATE:复制最近的边缘像素。 cv2.BORDER_REFLECT:以边缘为轴对称反射。 cv2.BORDER_WRAP:将图像作为一个环(循环重复)。 cv2.BORDER_REFLECT_101 或 cv2.BORDER_DEFAULT:以边缘外一个像素为轴对称反射。 cv2.BORDER_TRANSPARENT:边框区域透明,仅支持某些场景。
示例:
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 添加蓝色边框 bordered_img = cv2.copyMakeBorder(img, 50, 50, 30, 30, cv2.BORDER_CONSTANT, value=(255, 0, 0)) # 显示结果 # cv2.imshow('image', img) cv2.imshow('image', bordered_img) cv2.waitKey(0) cv2.destroyAllWindows()
十、图像算数运算
1、加法:将两幅图像的对应像素相加
import cv2 import numpy as np # 读取图像 img1 = cv2.imread('image1.jpg') img2 = cv2.imread('image2.jpg') # 饱和加法,值范围[0,255] result_add = cv2.add(img1, img2) # 模运算加法,相加值%256(余数) result_mod = img1 + img2 # 显示结果 cv2.imshow('Saturated Add', result_add) cv2.imshow('Modulo Add', result_mod) cv2.waitKey(0) cv2.destroyAllWindows()
2、减法: 将一幅图像的像素值从另一幅图像中减去
# 饱和减法,最小值为0,无负数 result_sub = cv2.subtract(img1, img2) # 模运算减法,值取模(%256) result_mod_sub = img1 - img2 cv2.imshow('Saturated Subtract', result_sub) cv2.imshow('Modulo Subtract', result_mod_sub) cv2.waitKey(0) cv2.destroyAllWindows()
3、乘法:对应像素相乘,常用于图像遮罩或权重调整
# 图像乘法 result_mul = cv2.multiply(img1, 0.5) # 缩小亮度 cv2.imshow('Multiply', result_mul) cv2.waitKey(0) cv2.destroyAllWindows()
4、除法:对应像素相除,用于亮度调整或归一化
# 图像除法 result_div = cv2.divide(img1, 2) # 降低亮度 cv2.imshow('Divide', result_div) cv2.waitKey(0) cv2.destroyAllWindows()
5、图像融合:加权融合
(权重和偏置)
# 图像融合 alpha = 0.7 # 权重 beta = 0.3 # 权重 result_blend = cv2.addWeighted(img1, alpha, img2, beta, 0) cv2.imshow('Blended Image', result_blend) cv2.waitKey(0) cv2.destroyAllWindows()
6、按位运算:与、或、异或、反
# 读取图像和掩码 mask = cv2.imread('mask.jpg', 0) # 按位与 # mask:掩膜,可选,用于指定哪些像素参与操作(0 表示不参与,非零表示参与) # 同第十二点inRange result_and = cv2.bitwise_and(img1, img2, mask=mask) # 按位或 result_or = cv2.bitwise_or(img1, img2) # 按位异或 result_xor = cv2.bitwise_xor(img1, img2) # 按位反 result_not = cv2.bitwise_not(img1, img2) cv2.imshow('Bitwise AND', result_and) cv2.imshow('Bitwise OR', result_or) cv2.imshow('Bitwise XOR', result_xor) cv2.imshow('Bitwise NOT', result_not) cv2.waitKey(0) cv2.destroyAllWindows()
十一、时间测量
# 方法一 import cv2 as cv import numpy as np # 获取起始时间 # 表示从某个特定事件(通常是操作系统启动)开始到调用此函数时经过的计时周期数(clock cycles) e1 = cv.getTickCount() # 执行代码块 img = np.zeros((5000, 5000, 3), np.uint8) for i in range(5): img = cv.GaussianBlur(img, (5, 5), 0) # 获取结束时间 e2 = cv.getTickCount() # 计算运行时间 # cv.getTickFrequency()返回系统时钟的频率(以每秒的时钟周期数为单位)。即:1 秒内的时钟周期数。 # 下面的代码是计算时间 time = (e2 - e1) / cv.getTickFrequency() print(f"代码执行时间: {time:.5f} 秒")
# 方法二 import time start = time.time() # 你的执行代码 end = time.time() print(f"运行时间: {end - start:.5f} 秒")
cv.getTickCount
和 cv.getTickFrequency
的实现是跨平台的,适合在不同系统上使用。- 时钟频率: 处理器每秒"滴答"的次数,表示它的速度。
- 时钟周期: 每一个"滴答"表示一个最小时间片段。
- 时钟周期数: 如果代码运行了 5 个"滴答",就是 5 个时钟周期数。
十二、改变颜色空间
1、cv2.cvtColor颜色空间转换
源式:cv.cvtColor(src, code[, dst[, dstCn]]) -> dst 用法:cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) src:输入图像(必须是一个有效的 NumPy 数组)。 code:转换代码,定义从哪种颜色空间转换到哪种颜色空间。 dst:输出图像(可选)。 dstCn:目标图像的通道数(可选,大部分情况下自动决定)。
cv2.COLOR_BGR2GRAY
:BGR → 灰度。
cv2.COLOR_BGR2RGB
:BGR → RGB。
cv2.COLOR_BGR2HSV
:BGR → HSV(色调、饱和度、明度)。
cv2.COLOR_HSV2BGR
:HSV → BGR。
cv2.COLOR_GRAY2BGR
:灰度 → BGR。
import cv2 # 读取彩色图像 img = cv2.imread('image.jpg') # 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cv2.imshow('Gray Image', gray) cv2.waitKey(0) cv2.destroyAllWindows()
2、cv2.inRange颜色范围过滤
源式:cv.inRange(src, lowerb, upperb) -> dst 用法:mask = cv2.inRange(hsv, lower_blue, upper_blue) src:输入图像(通常是 HSV 或 BGR 图像)。 lowerb:下边界值(每个通道的最小值)。 upperb:上边界值(每个通道的最大值)。 dst:输出的二值图像,像素值为 0 或 255。
# 转换为 HSV hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 定义蓝色的范围 lower_blue = (110, 50, 50) # H=110, S=50, V=50 upper_blue = (130, 255, 255) # H=130, S=255, V=255 # 创建掩膜 mask = cv2.inRange(hsv, lower_blue, upper_blue) cv2.imshow('Blue Mask', mask) cv2.waitKey(0) cv2.destroyAllWindows()
综合示例:提取绿色区域并显示
import cv2 import numpy as np # 读取图像 img = cv2.imread('image.jpg') # 转换为 HSV 颜色空间 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 定义绿色的范围 lower_green = (40, 40, 40) upper_green = (80, 255, 255) # 创建掩膜 mask = cv2.inRange(hsv, lower_green, upper_green) # 将掩膜作用到原始图像 result = cv2.bitwise_and(img, img, mask=mask) # 显示结果 cv2.imshow('Original', img) cv2.imshow('Mask', mask) cv2.imshow('Filtered', result) cv2.waitKey(0) cv2.destroyAllWindows()
十三、图像几何变换
1、图像平移
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 平移矩阵 M tx, ty = 100, 50 # 分别表示横向和纵向的偏移量 M = np.float32([[1, 0, tx], [0, 1, ty]]) # 平移矩阵,固定格式 # 应用平移 translated = cv2.warpAffine(img, M, (cols, rows)) # 图像宽高决定输出图像的大小 cv2.imshow('image', translated) cv2.waitKey(0) cv2.destroyAllWindows()
warpAffine
参数:- 第一个参数是输入图像。
- 第二个参数是变换矩阵 M。
- 第三个参数是输出图像的大小,格式为 (width,height)。
2、图像缩放
import cv2 # 读取图像 img = cv2.imread('image.jpg') # 缩放操作 resized = cv2.resize(img, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_LINEAR) # 放大 resized_small = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR) # 缩小 cv2.imshow('Resized Image', resized) cv2.imshow('Resized Smaller Image', resized_small) cv2.waitKey(0) cv2.destroyAllWindows()
resize参数:
fx
:横向放大
fy
:纵向放大
- interpolation,插值方法
cv2.INTER_NEAREST
:最近邻插值,速度快但可能出现锯齿。cv2.INTER_LINEAR
:双线性插值(默认),适合缩放。cv2.INTER_CUBIC
:三次插值,效果更好但速度较慢。cv2.INTER_LANCZOS4
:适合高质量的缩放。
3、图像旋转
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 旋转矩阵 center = (cols // 2, rows // 2) # 定义旋转中心 angle = 45 # 旋转角度 scale = 1 # 缩放比例 M = cv2.getRotationMatrix2D(center, angle, scale) # 获取旋转矩阵 # 旋转操作 rotated = cv2.warpAffine(img, M, (cols, rows)) cv2.imshow('image', rotated) cv2.waitKey(0) cv2.destroyAllWindows()
getRotationMatrix2D参数:
cneter
:选择中心
angle
:选择角度
scale
:缩放比例
4、仿射变换
仿射变换是保持直线不变的线性变换,可以实现旋转、缩放和剪切
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 定义三对点 pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 原图像的三个点 pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # 目标图像的三个点 # 仿射变换矩阵 M = cv2.getAffineTransform(pts1, pts2) # 仿射变换 affine = cv2.warpAffine(img, M, (cols, rows)) cv2.imshow('image', affine) cv2.waitKey(0) cv2.destroyAllWindows()
getAffineTransform参数:
pts1
:原图的三个点的坐标。
pts2
:目标图像中与pts1
对应的三个点的坐标。
5、透视变换
透视变换可以将图像从一个视角转换为另一个视角
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 定义四对点 pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]]) # 原图像的四个点 pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]]) # 目标图像的四个点 # 获取透视变换矩阵 M = cv2.getPerspectiveTransform(pts1, pts2) # 应用透视变换 perspective = cv2.warpPerspective(img, M, (cols, rows)) cv2.imshow('image', perspective) cv2.waitKey(0) cv2.destroyAllWindows()
getPerspectiveTransform参数:
pts1
:原图的四个点。
pts2
:目标图像中与pts1
对应的四个点。
十四、图像阈值
1、全局阈值
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 固定阈值二值化 # 第一个是使用的阈值,第二个输出是:阈值后的图像 _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) cv2.imshow('image', binary) cv2.waitKey(0) cv2.destroyAllWindows()
src
:图像,通常为单通道的灰度图像
thresh
:阈值
maxval
:二值化后像素值的最大值
type
:阈值处理类型cv.THRESH_BINARY
: 大于阈值的像素设为最大值,其他设为 0。cv.THRESH_BINARY_INV
: 大于阈值的像素设为 0,其他设为最大值。cv.THRESH_TRUNC
: 大于阈值的像素设为阈值,其他保持不变。cv.THRESH_TOZERO
: 小于阈值的像素设为 0,其他保持不变。cv.THRESH_TOZERO_INV
: 大于阈值的像素设为 0,其他保持不变。
2、局部阈值
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg', cv2.IMREAD_GRAYSCALE) rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 自适应阈值二值化 adaptive_mean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2) adaptive_gaussian = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) cv2.imshow('image', adaptive_mean) cv2.waitKey(0) cv2.destroyAllWindows()
src
:图像,必须是单通道的灰度图像
maxValue
:阈值后的最大像素值
adaptiveMethod
:阈值计算方法cv.ADAPTIVE_THRESH_MEAN_C
: 采用邻域的均值。cv.ADAPTIVE_THRESH_GAUSSIAN_C
: 采用邻域像素值的加权均值(权重为高斯分布)。
thresholdType
:阈值类型cv.THRESH_BINARY
: 大于阈值的像素设为最大值,其他设为 0。cv.THRESH_BINARY_INV
: 大于阈值的像素设为 0,其他设为最大值。cv.THRESH_TRUNC
: 大于阈值的像素设为阈值,其他保持不变。cv.THRESH_TOZERO
: 小于阈值的像素设为 0,其他保持不变。cv.THRESH_TOZERO_INV
: 大于阈值的像素设为 0,其他保持不变。
blockSIze
:邻域大小,必须是奇数
C
: 一个常数,从计算得到的阈值中减去此值,调整阈值大小
3、总结
特点 | cv.threshold | cv.threshold |
适用场景 | 光照均匀的图像 | 光照不均的图像 |
阈值类型 | 固定的全局阈值 | 局部计算阈值 |
复杂度 | 简单,效率高 | 相对复杂,计算量大 |
阈值计算依据 | 固定值 | 局部均值或加权均值 |
十五、图像模糊/图像平滑
1、平均平滑
利用像素邻域的平均值来替代当前像素值,从而减少图像中的随机噪声
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 平均平滑 kernel_size = (50, 50) smoothed = cv2.boxFilter(img, -1, kernel_size) # 深度一般为-1 # 或者 smoothed = cv2.blur(img, kernel_size) cv2.imshow("image_ori", img) cv2.imshow('image', smoothed) cv2.waitKey(0) cv2.destroyAllWindows()
img
:图像
kernel_size
:滤波器矩阵。滤波器越大,平滑效果越强,但会导致图像变模糊
2、高斯平滑
利用高斯分布的权重来计算像素值的加权平均值,中心像素权重大,边缘像素权重小。高斯平滑对高斯噪声效果更好。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) gaussian = cv2.GaussianBlur(img, (19, 19), sigmaX=1) cv2.imshow("image_ori", img) cv2.imshow('image', gaussian) cv2.waitKey(0) cv2.destroyAllWindows()
img
:图像
ksize
:高斯核(滤波器)矩阵,只能是单数
sigmaX
:高斯核在 X 方向的标准差,控制平滑强度。如果为0
,OpenCV 会根据核大小自动计算。
3、中值平滑
利用邻域内像素值的中值来替代中心像素值,对椒盐噪声有很好的抑制效果。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 中值平滑 median = cv2.medianBlur(img, 21) cv2.imshow("image_ori", img) cv2.imshow('image', median) cv2.waitKey(0) cv2.destroyAllWindows()
img
:图像
ksize
:滤波器(单个值,且必须为奇数)
4、双边滤波
在平滑图像的同时保留边缘细节,对比度较高的区域(如边缘)不会被模糊。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 双边滤波 bilateral = cv2.bilateralFilter(img, 19, 75, 75) cv2.imshow("image_ori", img) cv2.imshow('image', bilateral) cv2.waitKey(0) cv2.destroyAllWindows()
img
:图像
- 第二个参数
d
:滤波器的直径(像素邻域的大小)
- 第三个参数
sigmaColor
:颜色空间的滤波 sigma 值
- 第四个参数
sigmaSpace
:坐标空间的滤波 sigma 值
5、自定义卷积核
2D卷积(图像过滤) 是计算机视觉中一个重要的操作,用于对图像进行过滤、特征提取或特定效果处理。通过将一个小矩阵(称为卷积核或滤波器)与图像逐像素相乘并求和,实现对图像的增强或抑制。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 自定义模糊 kernel = np.ones((5, 5), np.float32) / 25 # 自定义卷积核 custom_blurred = cv2.filter2D(img, -1, kernel) cv2.imshow("image_ori", img) cv2.imshow('image', custom_blurred) cv2.waitKey(0) cv2.destroyAllWindows()
- 卷积核大小: 通常为奇数(如 3×3 或 5×5),便于以中心像素为基准。
- 边缘处理: 卷积核超出图像边界时,可以使用填充(
cv.BORDER_CONSTANT
或cv.BORDER_REFLECT
)。
- 计算效率: 大核可能会增加计算复杂度,但可以减少处理噪声的影响。
- 效果调节: 通过调整卷积核的值或大小,可以控制滤波效果。
- 2D卷积 是通过卷积核对图像进行局部加权操作,实现平滑、锐化、边缘检测等功能
6、总结
模糊方法 | 特点 | 适用场景 |
平均模糊 | 简单高效,但会模糊边缘 | 去除轻微噪声 |
高斯模糊 | 权重分布更自然,适合处理高斯噪声 | 去除高斯噪声 |
中值模糊 | 边缘保留好,对椒盐噪声有极佳效果 | 去除椒盐噪声 |
双边模糊 | 平滑效果好,同时保留边缘细节 | 保留细节的降噪 |
自定义模糊 | 灵活实现多种卷积核效果 | 特殊需求,锐化或特定模糊效果 |
十六、生成结构元素(卷积核)
a
:设定卷积核的形状MORPH_RECT
(函数返回矩形卷积核)MORPH_CROSS
(函数返回十字形卷积核)MORPH_ELLIPSE
(函数返回椭圆形卷积核
b
设定卷积核的大小(x,y)
的形式表示,表示卷积核有x行,y列
c
表示描点的位置,默认为中心点(-1, -1)
,表示描点位于中心
# 接下文《形态学》使用或上文《图像模糊/图像平滑使用》 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
十七、形态学
1、腐蚀
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) kernel = np.ones((5, 5), np.uint8) erosion = cv2.erode(img, kernel, iterations=1) cv2.imshow("image_ori", img) cv2.imshow('image', erosion) cv2.waitKey(0) cv2.destroyAllWindows()
src
:图像
kernel
:结构元素(卷积核)
dst
:输出图像(可选)
anchor
: 锚点,指定结构元素的参考点(默认值是(-1, -1)
,表示使用结构元素的中心)。
iterations
: 腐蚀的次数,默认值是1
。
borderType
: 边界模式,定义边界像素的处理方式(默认为cv2.BORDER_CONSTANT
)。
borderValue
: 边界填充值,仅在borderType
为cv2.BORDER_CONSTANT
时有效。
2、膨胀
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) kernel = np.ones((5, 5), np.uint8) dilation = cv2.dilate(img, kernel, iterations=1) cv2.imshow("image_ori", img) cv2.imshow('image', dilation) cv2.waitKey(0) cv2.destroyAllWindows()
src
: 输入图像。
kernel
: 结构元素,用于定义膨胀的形状和大小。
dst
: 输出图像(可选)。
anchor
: 锚点,指定结构元素的参考点(默认值是(-1, -1)
,表示使用结构元素的中心)。
iterations
: 膨胀的次数,默认值是1
。
borderType
: 边界模式。
borderValue
: 边界填充值。
3、开运算、闭运算、梯度、顶帽和黑帽
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) kernel = np.ones((5, 5), np.uint8) opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) cv2.imshow("image_ori", img) cv2.imshow('image', opening) cv2.waitKey(0) cv2.destroyAllWindows()
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 开运算 closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # 闭运算 gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 形态学梯度 tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) # 顶帽 blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) # 黑帽
src
: 输入图像。
op
: 操作类型:cv2.MORPH_OPEN
: 开运算(先腐蚀后膨胀)。cv2.MORPH_CLOSE
: 闭运算(先膨胀后腐蚀)。cv2.MORPH_GRADIENT
: 形态学梯度(膨胀与腐蚀之差)。cv2.MORPH_TOPHAT
: 顶帽运算(原图减去开运算的结果)。cv2.MORPH_BLACKHAT
: 黑帽运算(闭运算的结果减去原图)。
kernel
: 结构元素,定义操作的形状和大小。
dst
: 输出图像(可选)。
anchor
: 锚点。
iterations
: 操作的次数,默认值是1
。
borderType
: 边界模式。
borderValue
: 边界填充值。
4、总结
形态学转换 是图像处理中的一种技术,通常应用于二值图像(black-and-white images),也可以用于灰度图像。它基于 图像形状 的特性,主要用于处理噪声、分割、形状分析等任务
类型 | 作用 | 原理 | 应用 |
腐蚀 | 缩小图像中的前景(白色区域),去掉边缘的白噪声 | 结构元素在前景区域移动,只有当结构元素的覆盖区域完全在前景内时,中心像素才保留白色 | 减少小的噪声、分离粘连的物体 |
膨胀 | 扩大图像中的前景(白色区域),填补小孔 | 结构元素在背景区域移动,只要有一部分结构元素与前景接触,中心像素就变成白色 | 填充物体中的细小空洞、连接断裂的部分 |
开运算 | 先腐蚀后膨胀,用于去除图像中的小噪声,同时保持整体形状不变 | 去掉小的孤立白色区域(噪声) | 清理背景中的噪声 |
闭运算 | 先膨胀后腐蚀,用于填补物体中的小孔,同时保持整体形状不变 | 填补前景中的小黑洞 | 增强图像的连接性 |
形态学梯度 | 计算膨胀和腐蚀的差值,提取物体边界 | 前景的边界由膨胀的区域减去腐蚀的区域得到 | 边缘检测 |
顶帽 | 提取比背景更亮的区域 | 原图减去其开运算的结果 | 突出前景中的小的高亮区域 |
黑帽 | 提取比背景更暗的区域 | 闭运算的结果减去原图 | 突出前景中的小的暗色区域 |
十八、图像梯度
1、导数计算方法
cv2.Sobel
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # Sobel 梯度计算 sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # x 方向 sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # y 方向 # 转换为可视化形式 sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.convertScaleAbs(sobely) # 组合 x 和 y 梯度 sobel_combined = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0) cv2.imshow("image_ori", img) cv2.imshow('image', sobel_combined) cv2.waitKey(0) cv2.destroyAllWindows()
src
: 输入图像,通常为灰度图像
ddepth
: 输出图像的深度(如cv2.CV_64F
)
dx
: x 方向导数的阶数(1 表示一阶导数)
dy
: y 方向导数的阶数(1 表示一阶导数)
ksize
: Sobel 算子的核大小,通常为奇数(如 3、5、7)
2、
cv2.Sobel
优化=》cv2.Scharr
优化了核大小为 3 的情况,计算结果更精确
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # Sobel 梯度计算 sobelx = cv2.Scharr(img, cv2.CV_64F, 1, 0) # x 方向 sobely = cv2.Scharr(img, cv2.CV_64F, 0, 1) # y 方向 # 转换为可视化形式 sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.convertScaleAbs(sobely) # 组合 x 和 y 梯度 sobel_combined = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0) cv2.imshow("image_ori", img) cv2.imshow('image', sobel_combined) cv2.waitKey(0) cv2.destroyAllWindows()
3、二阶导数
cv2.Laplacian
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) laplacian = cv2.Laplacian(img, cv2.CV_64F) laplacian = cv2.convertScaleAbs(laplacian) cv2.imshow("image_ori", img) cv2.imshow('image', laplacian) cv2.waitKey(0) cv2.destroyAllWindows()
src
: 输入图像。
ddepth
: 输出图像的深度。
ksize
: 拉普拉斯算子的核大小(可选)
4、梯度的可视化
梯度计算结果通常会包含正负值,而图像显示需要非负值
sobelx = cv2.convertScaleAbs(sobelx)
归一化处理:
使用
cv2.normalize
将梯度值归一化到 0-255 范围。十九、图像深度(ddepth
参数)
ddepth
参数)接上一条《图像梯度》
ddepth
参数是 OpenCV 中与深度处理相关的重要参数,广泛用于图像梯度计算函数CV_8U
: 无符号 8 位整数 (0-255),常用于灰度图或 RGB 图像- 输出为 8 位无符号整数,但在梯度计算中很少直接使用。
- 如果梯度结果包含负值或大于 255 的值,可能导致结果截断或溢出。
CV_16U
: 无符号 16 位整数
CV_16S
: 有符号 16 位整数- 计算结果为 16 位有符号整数,适用于需要存储负值的场景。
CV_32F
: 32 位浮点数- 计算结果为 32 位浮点数,适用于某些精度要求较低但需要浮点数输出的场景。
CV_64F
: 64 位浮点数- 计算高精度梯度值,结果为 64 位浮点数。
- 推荐用于后续需要处理梯度信息的场景(如图像分析或特征提取)。
作用:
ddepth
指定输出图像的深度。- 在梯度计算中,结果的数值可能超出原始图像的深度范围。
- 例如,梯度计算可能会产生负值或大于 255 的值。
- 如果原始图像是
CV_8U
格式,梯度结果不能直接存储在相同格式中。
- 通过设置
ddepth
,可以确保输出图像能正确存储计算结果。
总结:
- 如果需要精确存储梯度值,建议使用
CV_64F
或CV_32F
。
- 如果只是用于显示或处理轻量化结果,可以使用
CV_16S
。
结果分析:
CV_8U
:- 梯度结果截断到 0-255 范围,无法表示负值。
- 适合直接显示图像,但信息丢失。
CV_16S
:- 可存储负值,适合后续处理。
- 显示时需通过
cv2.convertScaleAbs
转换。
CV_64F
:- 高精度计算结果,适合分析或进一步数学处理。
- 显示时同样需转换为可视化形式。
二十、边缘检测
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg', 0) rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 使用 Canny 边缘检测 threshold1 = 100 # 低阈值 threshold2 = 300 # 高阈值 edges = cv2.Canny(img, threshold1, threshold2) cv2.imshow("image_ori", img) cv2.imshow('image', edges) cv2.waitKey(0) cv2.destroyAllWindows()
参数解释:
image
(输入图像)- 要进行边缘检测的灰度图像。通常是单通道的图像(如灰度图像)。如果输入的是彩色图像,必须先转换为灰度图像。
threshold1
(低阈值)- 低阈值,用于边缘连接。在双阈值检测过程中,小于此值的像素会被认为是非边缘。
threshold2
(高阈值)- 高阈值,用于边缘检测。大于此值的像素会被认为是强边缘。
edges
(可选输出图像)- 输出图像,保存检测到的边缘。默认为None,表示返回一个新图像。
aperttureSize
(Sobel算子大小)- 用于计算梯度的Sobel算子的大小。可以选择3,5,7。默认值为3。该值决定了梯度计算时的局部区域大小
L2gradient
(是否使用L2范数计算梯度)- 默认值False,表示使用L1范数来计算梯度
- 设置为True,则使用L2范数来计算梯度幅值,即
sqrt(Gx^2 + Gy^2)
阈值选择:
- 低阈值 (
threshold1
): - 如果某个像素的梯度幅值低于低阈值,它会被认为是非边缘。
- 如果该像素的梯度幅值高于高阈值,且与强边缘相连,它会被认为是边缘。
- 高阈值 (
threshold2
): - 高于高阈值的像素会被认为是强边缘,保留为边缘。
- 低于高阈值、但与强边缘连接的像素也会被认为是边缘。
二十一、图像金字塔
图像处理中的多尺度表示技术,用于描述图像在不同分辨率下的层次结构。它通过一系列分辨率逐渐降低的图像来表示原始图像的信息,通常用于特征提取、图像分析、图像压缩等任务中
1、高斯金字塔
高斯金字塔是通过对原始图像反复进行下采样和模糊(通常使用高斯模糊)来构建的。每一层图像都比上一层的分辨率低,表示原图像的不同尺度。
构建过程:
- 基础层: 第一层是原始图像本身
- 模糊和下采样: 每一层通过对上一层进行高斯模糊,然后下采样(通常是将每个 2x2 的块降为一个像素)来生成下一层
- 继续迭代: 重复上述操作直到达到所需的分辨率或图像尺寸
优点:
- 高斯金字塔有助于去除图像中的噪声。
- 提供了图像的多尺度信息,能够处理图像在不同尺度下的特征。
用途:
- 图像金字塔在多尺度特征提取中的应用: 比如,进行物体检测时,可以在不同尺度下检测物体。
- 图像压缩: 用于分层编码和压缩。
- 图像匹配: 在不同尺度下匹配相同的图像区域。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg', 0) rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 创建高斯金字塔 layer = img.copy() gp = [layer] # 逐层下采样,创建金字塔 for i in range(6): layer = cv2.pyrDown(layer) gp.append(layer) # 显示金字塔各层 for i, layer in enumerate(gp): cv2.imshow(f'Layer {i}', layer) # cv2.imshow("image_ori", img) # cv2.imshow('image', edges) cv2.waitKey(0) cv2.destroyAllWindows()
2、拉普拉斯金字塔
拉普拉斯金字塔是在高斯金字塔的基础上进一步处理得到的。它是通过从高斯金字塔的相邻两层图像之间计算差异来生成的,表示图像的细节信息。
构建过程:
- 生成高斯金字塔: 先构建高斯金字塔。
- 计算相邻层之间的差异: 每一层的拉普拉斯金字塔图像是上一层和下一层图像的差异。具体来说,拉普拉斯金字塔的某一层是原始图像和其高斯金字塔上一层图像(经过上采样后)之间的差。
- 生成细节信息: 通过这种差异化的方式,拉普拉斯金字塔能够提取图像的细节信息(例如边缘和纹理)。
优点:
- 通过这种方式,拉普拉斯金字塔可以有效地捕捉图像的细节信息,如高频部分。
- 它对于图像重建和图像分解非常有用。
用途:
- 图像重建: 拉普拉斯金字塔是图像重建(例如无损压缩)和图像融合中的关键技术。
- 图像融合: 在图像拼接、合成等任务中,拉普拉斯金字塔常常用于平滑过渡,避免拼接线。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg', 0) rows, cols = img.shape[:2] # 获取图像的行数和列数 # cv2.namedWindow("image", 0) # cv2.resizeWindow("image", 800, 600) # # cv2.namedWindow("image_ori", 0) # cv2.resizeWindow("image_ori", 800, 600) # 创建高斯金字塔 layer = img.copy() gaussian_pyramid = [layer] for i in range(6): layer = cv2.pyrDown(layer) gaussian_pyramid.append(layer) # 创建拉普拉斯金字塔 laplacian_pyramid = [] for i in range(5, 0, -1): # 从高斯金字塔的倒数第二层开始 expanded = cv2.pyrUp(gaussian_pyramid[i]) expanded = cv2.resize(expanded, (gaussian_pyramid[i - 1].shape[1], gaussian_pyramid[i - 1].shape[0])) laplacian = cv2.subtract(gaussian_pyramid[i - 1].astype(np.float32), expanded.astype(np.float32)) # 对差值结果进行标准化,增强对比度 laplacian = np.clip((laplacian - laplacian.min()) / (laplacian.max() - laplacian.min()) * 255, 0, 255).astype( np.uint8) laplacian_pyramid.append(laplacian) # 显示拉普拉斯金字塔各层 for i, laplacian in enumerate(laplacian_pyramid): cv2.imshow(f'Laplacian Layer {i}', laplacian) # cv2.imshow("image_ori", img) # cv2.imshow('image', edges) cv2.waitKey(0) cv2.destroyAllWindows()
二十二、图像二值化(接下一条)
将图像转换为二值图像,在给定的阈值下将图像的像素值分为两部分:低于阈值的部分和高于阈值的部分。这个函数通常用于将灰度图像转换为二进制图像,便于后续的处理(例如,轮廓检测、边缘检测等)
参数解释:
src
:输入图像,必须是灰度图像(单通道)。
thresh
:阈值,所有小于这个值的像素将被设置为 0(黑色),大于该值的像素将被设置为maxval
(通常为 255)。
maxval
:用于设置阈值以上的像素的最大值(一般设为 255)。
type
:阈值类型,定义了如何处理像素值。常见的类型包括:cv2.THRESH_BINARY
:如果像素值大于阈值,将其设置为maxval
,否则设置为 0。用于基本的二值化。cv2.THRESH_BINARY_INV
:与THRESH_BINARY
相反,大于阈值的像素设置为 0,小于阈值的像素设置为maxval
。cv2.THRESH_TRUNC
:大于阈值的像素值被截断为阈值,其他像素值不变。cv2.THRESH_TOZERO
:大于阈值的像素保持不变,小于阈值的像素设置为 0。cv2.THRESH_TOZERO_INV
:与THRESH_TOZERO
相反。
返回值:
retval
:返回的阈值,通常不使用。
image
:经过阈值处理后的图像(输出结果),它是一个二值图像。
二十三、图像轮廓绘制
1、查找轮廓
import cv2 import numpy as np # 读取图像并转换为灰度 img = cv2.imread('img/img.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 打印轮廓数量 print(f"Number of contours found: {len(contours)}")
参数解释:
- image:输入图像,需要是一个二值图像(黑白图像)。通常通过阈值处理或边缘检测(如
cv2.Canny()
)生成。
- mode:轮廓的检索模式。常用的模式有:
cv2.RETR_EXTERNAL
:只检测外部轮廓。cv2.RETR_LIST
:检测所有的轮廓,但没有层级关系。cv2.RETR_TREE
:检测所有轮廓,并建立轮廓之间的层级关系。cv2.RETR_CCOMP
:检测所有轮廓,并组织成两级层级结构。
- method:轮廓逼近方法。常用的方法有:
cv2.CHAIN_APPROX_SIMPLE
:将轮廓的冗余点进行压缩,只保留端点。cv2.CHAIN_APPROX_NONE
:存储所有的轮廓点,不进行任何简化。
返回值:
contours
:检测到的轮廓列表,每个轮廓由一组点构成。
hierarchy
:层级信息,描述轮廓之间的关系(例如父子关系,是否是内嵌轮廓)。
2、绘制轮廓
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制所有轮廓 cv2.drawContours(img, contours, -1, (0, 255, 0), 2) # 显示图像 cv2.imshow('Contours', img) cv2.waitKey(0) cv2.destroyAllWindows()
参数解释:
- image:输入图像,将会在该图像上绘制轮廓。
- contours:轮廓数据,是由
cv.findContours()
返回的轮廓列表。
- contourIdx:绘制的轮廓索引。可以指定为:
-1
:绘制所有轮廓。>=0
:绘制指定索引的轮廓。
- color:轮廓的颜色(以 BGR 格式指定)。
- thickness:轮廓的线条宽度。如果为 -1,则填充轮廓内部。
二十四、图像轮廓特征
1、面积
轮廓的面积是轮廓内部所包含的像素点的总数,用于衡量轮廓的大小
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area = cv2.contourArea(cnt) print(area) cv2.waitKey(0) cv2.destroyAllWindows()
参数:
contour
:输入的轮廓点集(由cv2.findContours
获得)。
返回值:
area
:轮廓的面积。
应用场景:
- 判断物体的大小。
- 过滤小的噪声轮廓。
2、周长
轮廓的周长是轮廓边界的长度,可以用来判断轮廓的大小或形状复杂度。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: perimeter = cv2.arcLength(cnt, True) print(perimeter) cv2.waitKey(0) cv2.destroyAllWindows()
参数:
contour
:输入的轮廓点集。
isClosed
:布尔值,指定轮廓是否封闭(通常为True
)。
返回值:
perimeter
:轮廓的周长。
应用场景:
- 计算形状的复杂度。
- 判断形状是否是简单的几何图形。
3、特证矩
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: M = cv2.moments(cnt) print(M) cv2.waitKey(0) cv2.destroyAllWindows()
键名 | 含义 | 计算用途 |
m00 | 面积(零阶矩) | 轮廓的面积 |
m10 m01 | 一阶矩 | 用于计算质心坐标 |
m20 m02 m11 | 二阶矩 | 用于描述方向和惯性 |
mu20 mu02 mu11 | 中心矩 | 用于描述形状相对于质心的分布 |
nu20 nu02 nu11 | 归一化中心矩 | 用于形状描述和匹配,不受旋转、缩放影响 |
4、质心
质心是轮廓的中心点,即轮廓内部所有点的平均位置。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: moments = cv2.moments(cnt) if moments['m00'] != 0: cx = int(moments['m10'] / moments['m00']) cy = int(moments['m01'] / moments['m00']) print(cx, cy) cv2.waitKey(0) cv2.destroyAllWindows()
参数:
contour
:输入的轮廓点集。
moments['m10']
和moments['m01']
:质心的分量。
moments['m00']
:面积矩(轮廓的面积)。
返回值:
(cx, cy)
:质心的坐标。
应用场景:
- 跟踪物体位置。
- 描述形状的中心位置。
5、边界框
最小的包含轮廓的矩形框,可以是直的或旋转的
直矩形边界框
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) print(x, y, w, h) cv2.waitKey(0) cv2.destroyAllWindows()
(x, y)
:矩形左上角的坐标。
w
和h
:矩形的宽和高。
旋转矩形边界框
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: rect = cv2.minAreaRect(cnt) boxs = cv2.boxPoints(rect) print(boxs) cv2.waitKey(0) cv2.destroyAllWindows()
rect
:最小面积矩形的参数(中心、尺寸、旋转角度)。
box
:旋转矩形的四个顶点坐标。
最小外接圆
包含整个轮廓的最小圆
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: (x, y), radius = cv2.minEnclosingCircle(cnt) print(x, y, radius) cv2.waitKey(0) cv2.destroyAllWindows()
(x, y)
:圆心坐标。
radius
:圆的半径。
最小凸包
凸包是包含轮廓的最小多边形。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: hull = cv2.convexHull(cnt) print(hull) cv2.waitKey(0) cv2.destroyAllWindows()
6、形状近似
用更少的点逼近轮廓形状,描述轮廓的多边形近似
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: epsilon = 0.02 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) print(approx) cv2.waitKey(0) cv2.destroyAllWindows()
参数:
contour
:输入的轮廓点集。
epsilon
:近似精度,越大越简化。
True
:布尔值,指定是否封闭。
应用场景:
- 简化轮廓的复杂性。
- 识别多边形。
7、圆形度
用面积和周长的关系衡量形状的圆形程度。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: perimeter = cv2.arcLength(cnt, True) area = cv2.contourArea(cnt) if perimeter != 0 and area != 0: circularity = round((4 * np.pi * area) / (perimeter ** 2), 2) print(circularity) cv2.waitKey(0) cv2.destroyAllWindows()
- 完全圆形的值接近 1。
- 不规则形状的值较小。
总结:
- 面积、周长:衡量轮廓的大小。
- 质心:轮廓的中心位置。
- 边界框、凸包:描述轮廓的包围形状。
- 形状近似、圆形度:用于分析形状特性。
二十五、图像轮廓属性
1、坚实度
坚实度是等高线面积与其凸包面积之比;如果一个轮廓非常接近于其凸包(例如矩形或圆形),坚实度接近于 1;如果轮廓复杂,坚实度会较小。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area = cv2.contourArea(cnt) hull = cv2.convexHull(cnt) hull_area = cv2.contourArea(hull) if hull_area != 0: solidity = float(area) / hull_area print(solidity) cv2.waitKey(0) cv2.destroyAllWindows()
2、等效直径
等效直径是面积与轮廓面积相同的圆的直径;通过等效直径可以将复杂形状的轮廓与一个圆形进行对比。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area = cv2.contourArea(cnt) equi_diameter = np.sqrt(4 * area / np.pi) print(equi_diameter) cv2.waitKey(0) cv2.destroyAllWindows()
3、取向
取向是物体指向的角度。以下方法还给出了主轴和副轴的长度。
import cv2 import numpy as np # 创建一个带有椭圆的图像 image = np.zeros((400, 400), dtype=np.uint8) cv2.ellipse(image, (200, 200), (150, 100), 30, 0, 360, 255, -1) # 查找轮廓 contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 取第一个轮廓,拟合椭圆 cnt = contours[0] if len(cnt) >= 5: # 确保轮廓点数足够 (x, y), (MA, ma), angle = cv2.fitEllipse(cnt) # 打印椭圆参数 print(f"Center: ({x:.2f}, {y:.2f})") print(f"Major Axis (ma): {ma:.2f}") print(f"Minor Axis (MA): {MA:.2f}") print(f"Angle: {angle:.2f} degrees") # 可视化结果 result = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) cv2.ellipse(result, ((x, y), (MA, ma), angle), (0, 255, 0), 2) cv2.imshow('Fitted Ellipse', result) cv2.waitKey(0) cv2.destroyAllWindows() else: print("Not enough points to fit an ellipse.")
(x, y)
- 表示椭圆的中心点坐标,即椭圆的质心。
- 单位为像素,
x
和y
分别是横坐标和纵坐标。
(MA, ma)
- 分别是椭圆的短轴长度(
MA
,minor axis)和长轴长度(ma
,major axis)。 - 单位为像素。
angle
- 椭圆的旋转角度,表示长轴相对于水平线的夹角,单位是度。
- 角度是以逆时针方向测量的,范围为
[0, 180)
。
注意事项
- 输入要求
cv.fitEllipse()
适用于至少包含 5 个点的轮廓。如果点数不足,函数会报错。
- 适用场景
- 轮廓必须是封闭的,常用于分析形状的方向性和特征,比如物体的长宽比、方向角等。
4、掩码和像素点
掩模图像是与原图像尺寸相同的二值图像,其中特定轮廓区域的像素值为 255(白色),其余部分为 0(黑色)。
- 掩模图像用于提取轮廓内的像素值,计算平均强度等属性。
- 生成掩模图像时可以使用
cv2.drawContours
函数。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 # cv2.namedWindow("image", 0) # cv2.resizeWindow("image", 800, 600) # cv2.namedWindow("image_ori", 0) # cv2.resizeWindow("image_ori", 800, 600) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: # 创建与原图大小一致的黑色图像 mask = np.zeros_like(gray, dtype=np.uint8) # 绘制轮廓,将轮廓区域设置为白色 cv2.drawContours(mask, [cnt], -1, 255, thickness=2) # 显示掩模图像 cv2.imshow("Mask", mask) # cv2.imshow("image_ori", gray) # cv2.imshow('image', ) cv2.waitKey(0) cv2.destroyAllWindows()
5、平均颜色/平均强度
平均强度是指轮廓区域内像素的平均灰度值或颜色值,通常需要借助掩模图像进行计算。
- 对于灰度图,计算轮廓区域内的平均灰度值。
- 对于彩色图,计算每个通道的平均值。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: mask = np.zeros_like(gray, dtype=np.uint8) # 对于灰度图像 mean_val = cv2.mean(gray, mask=mask) print(f"Mean Intensity (Grayscale): {mean_val[0]}") # 对于彩色图像 mean_val_color = cv2.mean(img, mask=mask) print(f"Mean Intensity (BGR): {mean_val_color}") cv2.waitKey(0) cv2.destroyAllWindows()
6、极端点
极点是指对象的最顶部,最底部,最右侧和最左侧的点。
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg') rows, cols = img.shape[:2] # 获取图像的行数和列数 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化图像 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0]) rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0]) topmost = tuple(cnt[cnt[:, :, 1].argmin()][0]) bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0]) print(leftmost, rightmost, topmost, bottommost) cv2.waitKey(0) cv2.destroyAllWindows()
7、凸性缺陷
凸缺陷是轮廓与其凸包之间的最大偏差点
import cv2 import numpy as np # 读取图像 img = cv2.imread('img/img.jpg', 0) rows, cols = img.shape[:2] # 获取图像的行数和列数 cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) # 二值化图像 _, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: hull = cv2.convexHull(cnt, returnPoints=False) defects = cv2.convexityDefects(cnt, hull) for i in range(defects.shape[0]): # [起点、终点、最远点、到最远点的近似距离] s, e, f, d = defects[i, 0] start = tuple(cnt[s][0]) end = tuple(cnt[e][0]) far = tuple(cnt[f][0]) cv2.line(img, start, end, [0, 255, 0], 2) cv2.circle(img, far, 5, [0, 0, 255], -1) cv2.imshow('image', img) cv2.waitKey(0) cv2.destroyAllWindows()
- 如果图像中没有适合的轮廓(例如纯色图像),可能找不到轮廓,导致凸缺陷无法计算。
- 确保图像内容包含具有明显凸缺陷的形状,例如多边形、星形等。
- 如果轮廓的点数太少(少于 4 个点),
cv2.convexityDefects()
无法计算凸缺陷。
8、点多边形测试
找出图像中一点到轮廓线的最短距离。它返回的距离,点在轮廓线外时为负,点在轮廓线内时为正,点在轮廓线上时为零。
import cv2 import numpy as np # 创建一个图像 img = np.zeros((400, 400, 3), dtype=np.uint8) # 定义多边形轮廓 contour = np.array([[100, 100], [300, 100], [300, 300], [100, 300]]) # 绘制轮廓 cv2.polylines(img, [contour], isClosed=True, color=(255, 255, 255), thickness=2) # 测试点 test_points = [(200, 200), (50, 50), (100, 100)] for point in test_points: result = cv2.pointPolygonTest(contour, point, measureDist=False) if result > 0: label = "Inside" elif result == 0: label = "On Edge" else: label = "Outside" cv2.circle(img, point, 5, (0, 0, 255), -1) cv2.putText(img, label, (point[0] + 10, point[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) cv2.imshow("Point Polygon Test", img) cv2.waitKey(0) cv2.destroyAllWindows()
参数说明
contour
: 输入的轮廓,通常是cv2.findContours
返回的轮廓之一。
point
: 一个元组,表示要测试的点的坐标(x, y)
。
measureDist
: 一个布尔值:True
:返回点到轮廓的最短距离。False
:只返回点的位置关系(在内部、边界上或外部)。
返回值
- 如果
measureDist=False
: > 0
:点在多边形内部。== 0
:点在多边形边界上。< 0
:点在多边形外部。
- 如果
measureDist=True
: - 返回点到轮廓的最短距离,正负符号仍代表位置关系(正:内部,负:外部)。
9、形状匹配
比较两个形状或两个轮廓,并返回一个显示相似性的度量。结果越低,匹配越好
import cv2 import numpy as np # 创建两个图像 img1 = np.zeros((400, 400), dtype=np.uint8) img2 = np.zeros((400, 400), dtype=np.uint8) # 绘制两个形状 cv2.rectangle(img1, (100, 100), (300, 300), 255, -1) # 矩形 cv2.circle(img2, (200, 200), 100, 255, -1) # 圆形 # 查找轮廓 contours1, _ = cv2.findContours(img1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours2, _ = cv2.findContours(img2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 匹配形状 score = cv2.matchShapes(contours1[0], contours2[0], cv2.CONTOURS_MATCH_I1, 0) print(f"Shape Match Score: {score}") # 显示图像 cv2.imshow("Shape 1", img1) cv2.imshow("Shape 2", img2) cv2.waitKey(0) cv2.destroyAllWindows()
参数说明
contour1
和contour2
:- 输入的两个轮廓,通常由
cv2.findContours
提取。
method
:- 匹配方法,有以下三种:
cv2.CONTOURS_MATCH_I1
:基于 Hu 矩的差异。cv2.CONTOURS_MATCH_I2
:基于 Hu 矩平方差。cv2.CONTOURS_MATCH_I3
:基于 Hu 矩的角度差。
parameter
:- 目前未使用,可设为
0
。
返回值
- 返回一个非负浮点值,值越小表示两种形状越相似。
二十六、轮廓分层(查找轮廓-hierarchy参数)
hierarchy
是一个数组,每个轮廓对应一个元素,其中包含关于该轮廓的父轮廓、子轮廓和兄弟轮廓的信息。
对于没有子轮廓或父轮廓的轮廓,这些值将被设置为
-1
- Next:该轮廓的下一个兄弟轮廓的索引
- Previous:该轮廓的前一个兄弟轮廓的索引
- First Child:该轮廓的第一个子轮廓的索引(如果有的话)
- Parent:该轮廓的父轮廓的索引(如果有的话)
示例:
假设我们有一幅图像,其中有一个大矩形,中间包含一个小矩形,那么它们的层级结构可能如下所示:
- 大矩形:
[ -1, -1, 1, -1 ]
1
表示没有父轮廓和兄弟轮廓。1
表示它的子轮廓的索引。
- 小矩形(子轮廓):
[ -1, -1, -1, 0 ]
1
表示没有兄弟轮廓和子轮廓。0
表示它的父轮廓是大矩形(索引为 0)。
二十七、直方图
X轴上具有像素值(不总是从0到255的范围),在Y轴上具有图像中相应像素数的图
1、OpenCV
参数:
images
:输入图像。需要传递一个图像列表,即使只计算一个图像,也必须放在列表中。例如:[img]
。
channels
:通道索引,表示计算哪个通道的直方图。对于灰度图像,使用0
,对于彩色图像,使用0
、1
或2
(分别代表 B、G、R 通道)。
mask
:可选的掩模图像。如果设置了掩模,只有在掩模区域内的像素才会参与直方图的计算。默认值为None
,表示对整幅图像进行计算。
histSize
:直方图的大小。对于灰度图像,通常是256
(表示从 0 到 255 的灰度级别)。对于彩色图像,通常为256
(每个通道计算一个直方图)。
ranges
:直方图的灰度范围,通常是[0, 256]
,表示从 0 到 255 的像素值范围。
返回值:
- 返回一个多维数组(
numpy.ndarray
),包含每个像素值的频率。
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像 img = cv2.imread('img/img.jpg', 0) # 读取为灰度图像 # 计算直方图 hist = cv2.calcHist([img], [0], None, [256], [0, 256]) # 显示直方图 plt.plot(hist) plt.title("Histogram") plt.show()
2、PLT
参数解析:
x
:输入数据(数组)。这是直方图绘制的数据,可以是一个一维的 NumPy 数组、Python 列表或图像的像素值数据。
bins
:直方图的箱子(bin)数量,或者一个数组表示箱子的边界。默认为 10。如果是整数,表示箱子的数量;如果是一个数组,则表示每个箱子的边界。
range
:直方图的计算范围,默认为 None,表示数据的最小值和最大值。如果给定了范围range=(min, max)
,则只计算该范围内的频率。
density
:布尔值,表示是否将直方图标准化为概率密度。如果为True
,则每个箱子的面积表示相对频率。
weights
:一个数组,它的长度应该与x
相同。表示每个数据点的权重,默认值为None
。
cumulative
:布尔值,表示是否计算累积直方图。如果为True
,则直方图是累加的。
bottom
:指定每个箱子底部的高度,用于堆积直方图或堆积显示。
histtype
:直方图的类型。常见的类型有:'bar'
:条形图(默认值)。'step'
:阶梯图。'stepfilled'
:填充的阶梯图。
align
:决定箱子的对齐方式。'left'
、'mid'
、'right'
。
orientation
:方向。'vertical'
或'horizontal'
。
rwidth
:箱子的宽度比例,默认为None
,表示自动调整。
log
:布尔值,表示是否在 y 轴上使用对数尺度,适合显示具有大范围差异的数据。
color
:指定直方图的颜色。
label
:给直方图添加标签,通常用于图例。
stacked
:布尔值,表示是否堆叠多个直方图。
返回值:
plt.hist()
返回一个元组 (n, bins, patches)
:n
:包含每个箱子频数的数组。
bins
:每个箱子的边界数组。
patches
:包含绘制图形的Rectangle
对象,表示条形图的矩形。
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像 img = cv2.imread('img/img.jpg') plt.hist(img.ravel(), 256, (0, 256)) plt.show()
3、NumPy
参数:
a
:输入数据,通常是一个一维数组或图像数据(需要扁平化)。
bins
:指定直方图的箱子(bin)的数量或边界。如果是整数,则指定箱子的数量;如果是一个数组,则指定每个箱子的边界。
range
:直方图计算的范围,通常是(min, max)
,如果不指定,默认为数据的最小值和最大值。
返回值:
- 返回一个元组
(hist, bin_edges)
: hist
:包含每个箱子(bin)的频数。bins
:箱子的边界。
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像 img = cv2.imread('img.jpg', 0) # 读取为灰度图像 # 扁平化图像数据 flat_img = img.ravel() # 计算直方图 hist, bins = np.histogram(flat_img, bins=256, range=(0, 256)) # 显示直方图 plt.plot(hist) plt.title("Histogram") plt.show()
二十八、直方图均衡
直方图均衡的目标是通过调整图像的灰度值,使其均匀分布在整个灰度范围(0-255)内,增强图像的对比度。具体步骤如下:
- 计算原始图像的灰度直方图:统计图像中每个像素灰度值的频数。
- 计算累计分布函数(CDF):通过对直方图进行累积,可以得到每个灰度级的累计分布。
- 归一化:将累计分布函数归一化到 [0, 255] 的范围。
- 映射变换:将原始图像中的灰度值映射到新的灰度值。
通过这些步骤,图像的像素值范围被重新分布,提升了图像的对比度和细节,使图像的各个部分更加明显。
(1)灰度图
1、OpenCV
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像,转换为灰度图像 img = cv2.imread('img/img.jpg', cv2.IMREAD_GRAYSCALE) # 直方图均衡化 equ = cv2.equalizeHist(img) # 均衡化后的直方图 plt.hist(equ.ravel(), 256, (0, 256)) plt.title('Equalized Histogram') plt.show()
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像,转换为灰度图像 img = cv2.imread('img/img.jpg', cv2.IMREAD_GRAYSCALE) cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 原图直方图 plt.hist(img.ravel(), 256, (0, 256)) plt.title('Original Histogram') plt.show() # 直方图均衡化 equ = cv2.equalizeHist(img) # 均衡化后的直方图 plt.hist(equ.ravel(), 256, (0, 256)) plt.title('Equalized Histogram') plt.show() # 显示原图和均衡化后的图像 cv2.imshow('image_ori', img) cv2.imshow('image', equ) cv2.waitKey(0) cv2.destroyAllWindows()
2、NumPy
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像,转换为灰度图像 img = cv2.imread('img/img.jpg', cv2.IMREAD_GRAYSCALE) hist, bins = np.histogram(img.flatten(), 256, (0, 256)) cdf = hist.cumsum() cdf_normalized = cdf * float(hist.max()) / cdf.max() plt.plot(cdf_normalized, color='b') plt.hist(img.flatten(), 256, (0, 256), color='r') plt.xlim([0, 256]) plt.legend(('cdf', 'histogram'), loc='upper left') plt.show()
(2)彩色图
import cv2 import numpy as np # 读取彩色图像 img = cv2.imread('img/img.jpg') cv2.namedWindow("image", 0) cv2.resizeWindow("image", 800, 600) cv2.namedWindow("image_ori", 0) cv2.resizeWindow("image_ori", 800, 600) # 分离通道 b, g, r = cv2.split(img) # 对每个通道进行直方图均衡化 b_eq = cv2.equalizeHist(b) g_eq = cv2.equalizeHist(g) r_eq = cv2.equalizeHist(r) # 合并均衡化后的通道 img_eq = cv2.merge([b_eq, g_eq, r_eq]) # 显示结果 cv2.imshow('image_ori', img) cv2.imshow('image', img_eq) cv2.waitKey(0) cv2.destroyAllWindows()
效果展示
- 原始图像:图像的亮度和对比度不均匀,可能会有一些区域过暗或过亮。
- 均衡化后的图像:图像的灰度分布均匀,细节更加清晰,尤其是在低对比度的区域(如阴影或亮度较低的区域),这些区域的细节将变得更加明显。
注意事项
- 过度均衡化:对于某些图像,直方图均衡化可能会导致图像的细节丧失,或者引入过度的噪声。特别是在图像本身已经具有良好的对比度时,均衡化可能没有显著的效果,反而可能使图像看起来不自然。
- 局部均衡化(Adaptive Histogram Equalization):为了避免过度均衡化,可以使用局部均衡化方法(如
CLAHE
),该方法在图像的局部区域内应用均衡化,而不是全图均衡化,能够更好地保留细节。
二十九、二维直方图
二维直方图的每个元素表示两个通道某一对像素值(比如红色和绿色通道)出现的频率。换句话说,二维直方图告诉我们图像中哪些像素对出现的频率较高,哪些像素对比较稀有
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像,转换为灰度图像 img = cv2.imread('img/img.jpg') # 分离通道 b, g, r = cv2.split(img) # 计算二维直方图(例如红色和绿色通道的联合分布) hist_2d = cv2.calcHist([r, g], [0, 0], None, [256, 256], [0, 256, 0, 256]) # 显示二维直方图 plt.imshow(hist_2d, interpolation='nearest') plt.title("2D Histogram (R vs G)") plt.xlabel("Red channel") plt.ylabel("Green channel") plt.colorbar() plt.show()
cv2.split(img)
:将彩色图像分为三个通道,蓝色(B)、绿色(G)和红色(R)。
cv2.calcHist()
:计算图像的直方图。参数说明:[r, g]
:这是一个包含两个图像通道的列表,分别计算红色和绿色通道的联合分布。[0, 0]
:指定直方图的开始位置。对于二维直方图,我们使用[0, 0]
来表示从两个通道的起始位置开始计算。None
:不使用掩模(mask),表示对整个图像计算。[256, 256]
:指定每个通道的直方图的桶(bin)数目,这里我们将每个通道分成256个桶。[0, 256, 0, 256]
:指定每个通道的取值范围,这里是[0, 255]
,即图像的像素值范围。
总结
- 二维直方图是一种描述图像中两个变量(如两个颜色通道)之间关系的工具。
- 它通过统计图像中每一对像素的频率,能够帮助我们分析图像的颜色或纹理分布。
- 使用
cv2.calcHist()
函数可以方便地计算和显示二维直方图。
三十、直方图反投影
用于在图像中定位特定物体或区域,尤其在目标跟踪和图像分割中非常有效。它的基本思想是通过一个目标图像的直方图来估计新图像中每个像素属于该目标的可能性。换句话说,反投影是将目标的颜色分布映射到新的图像上,并标记出可能是目标的位置。
- image: 输入图像(通常是待检测的图像)。
- channels: 指定要使用的颜色通道。对于彩色图像,可以选择多个通道(如 BGR 或 HSV 的各个通道)。
- hist: 输入的目标图像的直方图。
- ranges: 颜色通道的范围。例如,对于8位图像,通常是
[0, 256]
。
- scale: 可选参数,指定是否需要缩放反投影的结果。
OpenCV版示例
import cv2 import numpy as np # 读取图像 target_img = cv2.imread('img/target.jpg') # 目标图像(用于生成目标的直方图) scene_img = cv2.imread('img/scene.jpg') # 待检测图像(场景图像) # 转换为 HSV 空间(因为 HSV 空间更适合颜色匹配) target_hsv = cv2.cvtColor(target_img, cv2.COLOR_BGR2HSV) scene_hsv = cv2.cvtColor(scene_img, cv2.COLOR_BGR2HSV) # 计算目标图像的直方图 target_hist = cv2.calcHist([target_hsv], [0, 1], None, [256, 256], [0, 256, 0, 256]) # 归一化直方图(这一步确保直方图的总和为1) cv2.normalize(target_hist, target_hist, 0, 255, cv2.NORM_MINMAX) # 计算场景图像的反投影 backproj = cv2.calcBackProject([scene_hsv], [0, 1], target_hist, [0, 256, 0, 256], 1) # 对反投影结果进行阈值处理,以便更清晰地显示匹配区域 _, thresholded = cv2.threshold(backproj, 50, 255, cv2.THRESH_BINARY) # 显示反投影结果 cv2.imshow('Back Projection', backproj) cv2.imshow('Thresholded', thresholded) cv2.waitKey(0) cv2.destroyAllWindows()
Numpy版示例
import cv2 import numpy as np # 读取图像 target_img = cv2.imread('img/target.jpg') # 目标图像 scene_img = cv2.imread('img/scene.jpg') # 场景图像 # 转换为 HSV 空间 target_hsv = cv2.cvtColor(target_img, cv2.COLOR_BGR2HSV) scene_hsv = cv2.cvtColor(scene_img, cv2.COLOR_BGR2HSV) # 计算目标图像的直方图 hist = cv2.calcHist([target_hsv], [0, 1], None, [256, 256], [0, 256, 0, 256]) # 归一化直方图 cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX) # 创建空白反投影图 backproj = np.zeros_like(scene_hsv[:, :, 0], dtype=np.float32) # 对场景图像的每个像素进行反投影 for i in range(scene_hsv.shape[0]): for j in range(scene_hsv.shape[1]): h = scene_hsv[i, j, 0] s = scene_hsv[i, j, 1] backproj[i, j] = hist[h, s] # 显示反投影结果 cv2.imshow('Manual Back Projection', backproj) cv2.waitKey(0) cv2.destroyAllWindows()