opencv安装
``` pip install opencv-python-headless -i https://pypi.tuna.tsinghua.edu.cn/simple pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple import cv2 cv2.__version__ '4.7.0' ``` - 常见问题 - ImportError: libGL.so.1: cannot open shared object file: No such file or directory - 解决方法: pip install opencv-python-headless

下载

 
pip download \
    --only-binary=:all: \
    --platform manylinux2014_x86_64 \
    --python-version 3.11 \
    --implementation cp \
    --abi cp311 \
    -d ./whl \
    "opencv-python-headless"
  
pip install --no-index --find-links=./whl opencv-python-headless
    

 
pip download \
  --only-binary=:all: \
  --platform manylinux2014_x86_64 \
  --python-version 3.11 \
  --implementation cp \
  --abi cp311 \
  -d ./whl \
  "opencv-python"

pip install --no-index --find-links=./whl opencv-python


 

    

 
https://docs.opencv.ac.cn/4.11.0/d6/d00/tutorial_py_root.html


 

  

 


opencv读取图像

 
cv2读取图像的格式 [H,W,C], 其中C为 [B,G,R]
读取结果数据类型 np.ndarray 

cv2读取图像示例

 
import cv2
image = cv2.imread("K:/tmp/a.png")#image :是返回提取到的图片的值
print(type(image),image.shape)  # <class 'numpy.ndarray'> (95, 381, 3)

tmp=image[0,1]
tmp
array([208, 203, 194], dtype=uint8)

tmp = tmp/255
tmp.round(7)
array([0.8156863, 0.7960784, 0.7607843])

matplotlib读取图像示例

 
from matplotlib import pyplot as plt 
img = plt.imread("K:/tmp/a.png")
print(type(img),img.shape)  <class 'numpy.ndarray'> (458, 604, 4)
    
img[0,1]
array([0.7607843 , 0.79607844, 0.8156863 , 1.        ], dtype=float32)

cv2.resize

 
cv2.resize(src=img,dsize=(32,32))

 
import cv2

img = cv2.imread("lingmao.jpg")
print(type(img),img.shape)  #class 'numpy.ndarray' (940, 940, 3)

img = cv2.resize(img,dsize=(32,32))
print(type(img),img.shape)  #class 'numpy.ndarray' (32, 32, 3)

OpenCV GUI特性
## 图像显示与保存 ### cv2.imshow() - 显示图像 ```python import cv2 # 读取图像 img = cv2.imread('image.jpg') # 创建窗口 cv2.namedWindow('window_name', cv2.WINDOW_NORMAL) cv2.resizeWindow('window_name', 800, 600) # 显示图像 cv2.imshow('window_name', img) # 等待按键 cv2.waitKey(0) # 0表示永久等待,其他数字表示毫秒数 # 销毁所有窗口 cv2.destroyAllWindows() ``` **窗口标志**: | 标志 | 说明 | |-----|------| | cv2.WINDOW_NORMAL | 可调整大小 | | cv2.WINDOW_AUTOSIZE | 自动适应图像大小 | | cv2.WINDOW_FULLSCREEN | 全屏显示 | ### cv2.imwrite() - 保存图像 ```python # 保存图像 cv2.imwrite('output.jpg', img) # 保存为不同格式 cv2.imwrite('output.png', img) # PNG格式 cv2.imwrite('output.bmp', img) # BMP格式 # 保存时指定质量(仅对JPEG有效) cv2.imwrite('output.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), 95]) # 保存时指定压缩级别(仅对PNG有效) cv2.imwrite('output.png', img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9]) ``` ### 完整示例 ```python import cv2 # 读取图像 img = cv2.imread('input.jpg') # 创建窗口并显示 cv2.namedWindow('Image', cv2.WINDOW_NORMAL) cv2.imshow('Image', img) # 等待按键,按下's'键保存,'q'键退出 key = cv2.waitKey(0) if key == ord('s'): cv2.imwrite('saved_image.jpg', img) print("图像已保存") cv2.destroyAllWindows() ```
## 视频操作 ### 读取视频文件 ```python import cv2 # 打开视频文件 cap = cv2.VideoCapture('video.mp4') # 检查是否成功打开 if not cap.isOpened(): print("无法打开视频文件") exit() # 获取视频属性 fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f"FPS: {fps}, 分辨率: {width}x{height}, 总帧数: {frame_count}") # 逐帧读取 while True: ret, frame = cap.read() # ret表示是否成功读取帧 if not ret: print("视频结束或读取失败") break # 处理帧 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 显示帧 cv2.imshow('Video', frame) # 按'q'退出 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows() ``` ### 使用摄像头 ```python import cv2 # 打开摄像头(0表示默认摄像头) cap = cv2.VideoCapture(0) # 设置分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame = cap.read() if not ret: print("无法获取帧") break # 水平翻转(镜像效果) frame = cv2.flip(frame, 1) cv2.imshow('Camera', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ``` ### 保存视频 ```python import cv2 # 打开视频文件或摄像头 cap = cv2.VideoCapture('input.mp4') # 获取视频属性 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) # 定义编码器和创建VideoWriter对象 fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 或使用 'XVID', 'MJPG' out = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height)) while True: ret, frame = cap.read() if not ret: break # 处理帧(例如:转换为灰度) # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # gray = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) # 写入帧 out.write(frame) cv2.imshow('Frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() out.release() cv2.destroyAllWindows() ``` ### 视频编码格式 | 编码器 | fourcc | 说明 | |-------|--------|------| | MP4 | 'mp4v' | MPEG-4 Part 2 | | XVID | 'XVID' | XVID MPEG-4 | | MJPG | 'MJPG' | Motion JPEG | | H264 | 'H264' | H.264 (需要额外支持) | | HEVC | 'HEVC' | H.265 (需要额外支持) |
## 鼠标事件 ### 鼠标回调函数 ```python import cv2 import numpy as np # 全局变量 drawing = False # 是否正在绘制 mode = True # True=矩形, False=线条 ix, iy = -1, -1 # 起始坐标 # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode if event == cv2.EVENT_LBUTTONDOWN: drawing = True ix, iy = x, y elif event == cv2.EVENT_MOUSEMOVE: if drawing == True: if mode: cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1) else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) elif event == cv2.EVENT_LBUTTONUP: drawing = False if mode: cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1) else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) # 创建黑色图像 img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') # 设置鼠标回调 cv2.setMouseCallback('image', draw_circle) while True: cv2.imshow('image', img) k = cv2.waitKey(1) & 0xFF # 按'm'切换模式 if k == ord('m'): mode = not mode # 按'ESC'退出 elif k == 27: break cv2.destroyAllWindows() ``` ### 鼠标事件类型 | 事件 | 说明 | |-----|------| | cv2.EVENT_MOUSEMOVE | 鼠标移动 | | cv2.EVENT_LBUTTONDOWN | 左键按下 | | cv2.EVENT_RBUTTONDOWN | 右键按下 | | cv2.EVENT_MBUTTONDOWN | 中键按下 | | cv2.EVENT_LBUTTONUP | 左键释放 | | cv2.EVENT_RBUTTONUP | 右键释放 | | cv2.EVENT_MBUTTONUP | 中键释放 | | cv2.EVENT_LBUTTONDBLCLK | 左键双击 | | cv2.EVENT_RBUTTONDBLCLK | 右键双击 | | cv2.EVENT_MBUTTONDBLCLK | 中键双击 | | cv2.EVENT_MOUSEWHEEL | 鼠标滚轮 | | cv2.EVENT_MOUSEHWHEEL | 鼠标水平滚轮 | ### 实用示例:选择ROI区域 ```python import cv2 # 全局变量 ref_point = [] cropping = False def crop_roi(event, x, y, flags, param): global ref_point, cropping if event == cv2.EVENT_LBUTTONDOWN: ref_point = [(x, y)] cropping = True elif event == cv2.EVENT_LBUTTONUP: ref_point.append((x, y)) cropping = False # 绘制矩形框 cv2.rectangle(image, ref_point[0], ref_point[1], (0, 255, 0), 2) cv2.imshow("image", image) # 读取图像 image = cv2.imread('image.jpg') clone = image.copy() cv2.namedWindow("image") cv2.setMouseCallback("image", crop_roi) while True: cv2.imshow("image", image) key = cv2.waitKey(1) & 0xFF # 按'r'重置 if key == ord('r'): image = clone.copy() # 按'c'裁剪 elif key == ord('c'): break # 裁剪并显示ROI if len(ref_point) == 2: roi = clone[ref_point[0][1]:ref_point[1][1], ref_point[0][0]:ref_point[1][0]] cv2.imshow("ROI", roi) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 轨迹条(Trackbar) ### 创建轨迹条 ```python import cv2 import numpy as np def nothing(x): pass # 创建黑色图像 img = np.zeros((300, 512, 3), np.uint8) cv2.namedWindow('image') # 创建轨迹条 cv2.createTrackbar('R', 'image', 0, 255, nothing) cv2.createTrackbar('G', 'image', 0, 255, nothing) cv2.createTrackbar('B', 'image', 0, 255, nothing) # 创建开关 switch = '0:OFF\n1:ON' cv2.createTrackbar(switch, 'image', 0, 1, nothing) while True: cv2.imshow('image', img) k = cv2.waitKey(1) & 0xFF if k == 27: break # 获取轨迹条位置 r = cv2.getTrackbarPos('R', 'image') g = cv2.getTrackbarPos('G', 'image') b = cv2.getTrackbarPos('B', 'image') s = cv2.getTrackbarPos(switch, 'image') if s == 0: img[:] = 0 else: img[:] = [b, g, r] cv2.destroyAllWindows() ``` ### 实用示例:亮度与对比度调整 ```python import cv2 def nothing(x): pass # 读取图像 img = cv2.imread('image.jpg') cv2.namedWindow('image') # 创建轨迹条 cv2.createTrackbar('Brightness', 'image', 100, 200, nothing) cv2.createTrackbar('Contrast', 'image', 100, 300, nothing) while True: # 获取轨迹条值 brightness = cv2.getTrackbarPos('Brightness', 'image') - 100 contrast = cv2.getTrackbarPos('Contrast', 'image') / 100 # 调整亮度和对比度 adjusted = cv2.convertScaleAbs(img, alpha=contrast, beta=brightness) cv2.imshow('image', adjusted) if cv2.waitKey(1) & 0xFF == 27: break cv2.destroyAllWindows() ``` ### 实用示例:颜色阈值调整 ```python import cv2 import numpy as np def nothing(x): pass # 读取图像 img = cv2.imread('image.jpg') hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.namedWindow('image') # 创建H、S、V范围轨迹条 cv2.createTrackbar('H Min', 'image', 0, 179, nothing) cv2.createTrackbar('H Max', 'image', 179, 179, nothing) cv2.createTrackbar('S Min', 'image', 0, 255, nothing) cv2.createTrackbar('S Max', 'image', 255, 255, nothing) cv2.createTrackbar('V Min', 'image', 0, 255, nothing) cv2.createTrackbar('V Max', 'image', 255, 255, nothing) while True: # 获取轨迹条值 h_min = cv2.getTrackbarPos('H Min', 'image') h_max = cv2.getTrackbarPos('H Max', 'image') s_min = cv2.getTrackbarPos('S Min', 'image') s_max = cv2.getTrackbarPos('S Max', 'image') v_min = cv2.getTrackbarPos('V Min', 'image') v_max = cv2.getTrackbarPos('V Max', 'image') # 创建掩码 lower = np.array([h_min, s_min, v_min]) upper = np.array([h_max, s_max, v_max]) mask = cv2.inRange(hsv, lower, upper) # 应用掩码 result = cv2.bitwise_and(img, img, mask=mask) # 显示结果 cv2.imshow('image', result) cv2.imshow('mask', mask) if cv2.waitKey(1) & 0xFF == 27: break cv2.destroyAllWindows() ```
图像基础操作
## 像素操作 ### 访问和修改像素值 ```python import cv2 import numpy as np # 读取图像 img = cv2.imread('image.jpg') # 访问单个像素 px = img[100, 100] print(px) # [B G R] # 访问单个通道 blue = img[100, 100, 0] print(blue) # B值 # 修改像素值 img[100, 100] = [255, 255, 255] # 或 img.item(100, 100, 0) = 255 # 更快的方法 # 获取像素值 value = img.item(100, 100, 0) print(value) # ROI(感兴趣区域)操作 roi = img[100:200, 100:200] img[0:100, 0:100] = roi # 将ROI复制到另一位置 ``` ### 图像属性 ```python import cv2 img = cv2.imread('image.jpg') # 图像尺寸 print(img.shape) # (height, width, channels) # 数据类型 print(img.dtype) # uint8 # 图像总像素数 print(img.size) # height * width * channels # 图像宽度和高度 height, width = img.shape[:2] print(f"高度: {height}, 宽度: {width}") ``` ### 图像通道操作 ```python import cv2 img = cv2.imread('image.jpg') # 分离通道 b, g, r = cv2.split(img) # 显示单个通道 cv2.imshow('Blue', b) cv2.imshow('Green', g) cv2.imshow('Red', r) # 合并通道 img_merged = cv2.merge([b, g, r]) # 使某一通道为0 img[:, :, 0] = 0 # 蓝色通道置零 img[:, :, 1] = 0 # 绿色通道置零 # 只保留红色通道 ``` ### 边界填充 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 常量填充 constant = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=[255, 0, 0]) # 镜像填充 reflect = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_REFLECT) # 扩展填充 wrap = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_WRAP) # 重复填充 replicate = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_REPLICATE) # 边界类型 """ cv2.BORDER_CONSTANT - 常数填充 cv2.BORDER_REFLECT - 镜像反射 (fedcba|abcdefgh|hgfedcb) cv2.BORDER_REFLECT_101 - 反射,不包含边界 (gfedcb|abcdefgh|gfedcba) cv2.BORDER_REPLICATE - 重复最后一个像素 (aaaaa|abcdefgh|hhhhhhh) cv2.BORDER_WRAP - 外包包装 (cdefgh|abcdefgh|abcdefg) cv2.BORDER_TRANSPARENT - 透明填充 """ ```
## 颜色空间转换 ### 颜色空间转换函数 ```python import cv2 # 读取BGR图像 img = cv2.imread('image.jpg') # BGR转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # BGR转HSV hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # BGR转RGB rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR转LAB lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) # BGR转YCrCb ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) # 灰度转BGR bgr = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) # HSV转BGR bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) ``` ### 常用颜色空间 | 颜色空间 | 说明 | 应用场景 | |---------|------|----------| | **BGR** | OpenCV默认格式 | 通用图像处理 | | **RGB** | 图像显示、深度学习 | matplotlib、PyTorch | | **GRAY** | 灰度图 | 边缘检测、特征提取 | | **HSV** | 色调、饱和度、明度 | 颜色分割、阈值处理 | | **LAB** | 亮度、A通道、B通道 | 颜色距离计算 | | **YCrCb** | 亮度、色度 | 视频压缩、人脸检测 | ### HSV颜色空间示例 ```python import cv2 import numpy as np # 读取图像 img = cv2.imread('image.jpg') hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 定义颜色范围(以蓝色为例) lower_blue = np.array([110, 50, 50]) upper_blue = np.array([130, 255, 255]) # 创建掩码 mask = cv2.inRange(hsv, lower_blue, upper_blue) # 应用掩码 result = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('Original', img) cv2.imshow('Mask', mask) cv2.imshow('Result', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 常用颜色HSV范围 | 颜色 | H范围 | S范围 | V范围 | |-----|------|------|------| | 红色 | 0-10, 170-180 | 43-255 | 46-255 | | 橙色 | 11-20 | 43-255 | 46-255 | | 黄色 | 21-37 | 43-255 | 46-255 | | 绿色 | 38-84 | 43-255 | 46-255 | | 青色 | 85-99 | 43-255 | 46-255 | | 蓝色 | 100-124 | 43-255 | 46-255 | | 紫色 | 125-150 | 43-255 | 46-255 | ### YCrCb颜色空间示例(肤色检测) ```python import cv2 import numpy as np # 读取图像 img = cv2.imread('face.jpg') ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) # 肤色范围 lower_skin = np.array([0, 133, 77]) upper_skin = np.array([255, 173, 127]) # 创建掩码 mask = cv2.inRange(ycrcb, lower_skin, upper_skin) # 形态学操作去噪 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 应用掩码 result = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('Skin Detection', result) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 图像算术运算 ### 图像加法 ```python import cv2 import numpy as np # 读取图像 img1 = cv2.imread('image1.jpg') img2 = cv2.imread('image2.jpg') # 需要确保两张图像大小相同 img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) # 方法1: cv2.add(饱和运算,超过255截断为255) result = cv2.add(img1, img2) # 方法2: numpy加法(模运算,超过256取模) result = img1 + img2 # 256 % 256 = 0 # 方法3: 加权混合 # result = α·img1 + β·img2 + γ alpha = 0.7 beta = 0.3 gamma = 0 result = cv2.addWeighted(img1, alpha, img2, beta, gamma) # 示例:图像渐变过渡 for i in range(101): alpha = i / 100 beta = 1 - alpha result = cv2.addWeighted(img1, alpha, img2, beta, 0) cv2.imshow('Transition', result) cv2.waitKey(50) cv2.destroyAllWindows() ``` ### 图像减法 ```python import cv2 # 读取图像 img1 = cv2.imread('image1.jpg') img2 = cv2.imread('image2.jpg') # 确保大小相同 img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) # 图像减法(检测差异) diff = cv2.subtract(img1, img2) # 绝对差值(更常用) abs_diff = cv2.absdiff(img1, img2) # 转换为灰度 gray_diff = cv2.cvtColor(abs_diff, cv2.COLOR_BGR2GRAY) # 阈值处理 _, threshold = cv2.threshold(gray_diff, 30, 255, cv2.THRESH_BINARY) cv2.imshow('Difference', threshold) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 按位运算 ```python import cv2 import numpy as np # 创建两个图像 img1 = np.zeros((300, 500, 3), np.uint8) img2 = np.zeros((300, 500, 3), np.uint8) # 绘制矩形和圆形 cv2.rectangle(img1, (50, 50), (250, 250), (255, 255, 255), -1) cv2.circle(img2, (250, 150), 100, (255, 255, 255), -1) # 按位与 bit_and = cv2.bitwise_and(img1, img2) # 按位或 bit_or = cv2.bitwise_or(img1, img2) # 按位异或 bit_xor = cv2.bitwise_xor(img1, img2) # 按位非 bit_not = cv2.bitwise_not(img1) cv2.imshow('AND', bit_and) cv2.imshow('OR', bit_or) cv2.imshow('XOR', bit_xor) cv2.imshow('NOT', bit_not) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:添加水印 ```python import cv2 # 读取主图像和水印 main_img = cv2.imread('main.jpg') logo = cv2.imread('logo.png') # 调整logo大小 logo = cv2.resize(logo, (100, 100)) # 获取ROI位置 h, w = logo.shape[:2] x, y = main_img.shape[1] - w - 10, main_img.shape[0] - h - 10 roi = main_img[y:y+h, x:x+w] # 创建掩码(假设logo有透明通道) if logo.shape[2] == 4: # 分离alpha通道 bgr = logo[:, :, :3] alpha = logo[:, :, 3] / 255.0 # 应用alpha混合 for c in range(3): roi[:, :, c] = roi[:, :, c] * (1 - alpha) + bgr[:, :, c] * alpha else: # 直接添加 main_img[y:y+h, x:x+w] = logo cv2.imshow('Watermarked', main_img) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 图像性能优化 ### 检查代码执行时间 ```python import cv2 import time # 方法1:使用time模块 img = cv2.imread('image.jpg') start = time.time() # 你的代码 result = cv2.GaussianBlur(img, (5, 5), 0) end = time.time() print(f"执行时间: {(end - start) * 1000:.2f} 毫秒") # 方法2:使用cv2.getTickCount e1 = cv2.getTickCount() # 你的代码 result = cv2.GaussianBlur(img, (5, 5), 0) e2 = cv2.getTickCount() time = (e2 - e1) / cv2.getTickFrequency() print(f"执行时间: {time * 1000:.2f} 毫秒") ``` ### OpenCV默认优化 ```python # OpenCV默认使用优化代码 # 可以禁用优化 cv2.setUseOptimized(False) # 检查是否启用优化 print(cv2.useOptimized()) # True/False # 重新启用 cv2.setUseOptimized(True) ``` ### 性能对比示例 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 测试1:直接像素访问 vs itemset e1 = cv2.getTickCount() for i in range(1000): img[100, 100] = [255, 255, 255] e2 = cv2.getTickCount() time1 = (e2 - e1) / cv2.getTickFrequency() e1 = cv2.getTickCount() for i in range(1000): img.itemset((100, 100, 0), 255) img.itemset((100, 100, 1), 255) img.itemset((100, 100, 2), 255) e2 = cv2.getTickCount() time2 = (e2 - e1) / cv2.getTickFrequency() print(f"直接访问: {time1*1000:.4f} ms") print(f"itemset: {time2*1000:.4f} ms") print(f"加速比: {time1/time2:.2f}x") ``` ### 向量化操作 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 慢:循环遍历每个像素 def slow_method(img): result = img.copy() h, w = img.shape[:2] for i in range(h): for j in range(w): result[i, j] = 255 - img[i, j] # 反色 return result # 快:向量化操作 def fast_method(img): return 255 - img # 性能测试 import time start = time.time() result1 = slow_method(img) print(f"慢方法: {(time.time() - start)*1000:.2f} ms") start = time.time() result2 = fast_method(img) print(f"快方法: {(time.time() - start)*1000:.2f} ms") # OpenCV内置函数更快 start = time.time() result3 = cv2.bitwise_not(img) print(f"OpenCV函数: {(time.time() - start)*1000:.2f} ms") ``` ### 内存优化技巧 ```python import cv2 import numpy as np # 1. 就地操作(原地修改) # 慢:创建新图像 result = cv2.GaussianBlur(img, (5, 5), 0) # 快:使用dst参数(就地操作) cv2.GaussianBlur(img, (5, 5), 0, dst=dst, borderType=cv2.BORDER_DEFAULT) # 2. 使用合适的图像类型 # 如果不需要255级灰度,可以使用更少的位数 img_8bit = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 3. 释放不需要的内存 large_image = None # 显式设置为None # 4. 使用生成器处理大量图像 def image_generator(image_paths): for path in image_paths: img = cv2.imread(path) if img is not None: yield img # 自动释放内存 # 使用生成器 for img in image_generator(image_list): # 处理图像 processed = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # img在这里自动释放 ``` ### 性能优化总结 | 技术 | 说明 | 加速效果 | |-----|------|----------| | **向量化操作** | 使用NumPy向量化代替循环 | 10-100x | | **OpenCV内置函数** | 使用优化过的OpenCV函数 | 2-10x | | **就地操作** | 避免创建临时变量 | 1.5-2x | | **减少数据拷贝** | 使用引用代替拷贝 | 2-5x | | **使用GPU** | 使用cuda支持的操作 | 10-50x | | **多线程** | 并行处理多个图像 | 核心数倍 |
图像阈值处理
## 简单阈值处理 ### cv2.threshold() 基本用法 ```python import cv2 import numpy as np # 读取图像并转为灰度 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 应用阈值 # 像素值 > threshold → maxval # 像素值 <= threshold → 0 ret, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 显示结果 cv2.imshow('Original', img) cv2.imshow('Threshold', thresh) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 阈值类型 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 不同阈值类型 ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 像素 > threshold → maxval,否则 → 0 ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 像素 > threshold → 0,否则 → maxval ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) # 像素 > threshold → threshold,否则不变 ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) # 像素 > threshold → 不变,否则 → 0 ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) # 像素 > threshold → 0,否则不变 # 显示所有结果 titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(6): cv2.imshow(titles[i], images[i]) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 阈值类型对比 | 类型 | 条件 | 结果 | |-----|------|------| | THRESH_BINARY | pixel > thresh | maxval, else 0 | | THRESH_BINARY_INV | pixel > thresh | 0, else maxval | | THRESH_TRUNC | pixel > thresh | thresh, else pixel | | THRESH_TOZERO | pixel > thresh | pixel, else 0 | | THRESH_TOZERO_INV | pixel > thresh | 0, else pixel | ### 实用示例:二值化文档 ```python import cv2 # 读取文档图像 img = cv2.imread('document.jpg', cv2.IMREAD_GRAYSCALE) # 应用高斯模糊 blur = cv2.GaussianBlur(img, (5, 5), 0) # 应用阈值 ret, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imshow('Original', img) cv2.imshow('Binary', thresh) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 自适应阈值 ### 自适应阈值原理 对于光照不均匀的图像,固定阈值效果不佳。自适应阈值根据像素邻域的均值或高斯加权和动态计算阈值。 ### cv2.adaptiveThreshold() 用法 ```python import cv2 import numpy as np # 读取图像 img = cv2.imread('text.jpg', cv2.IMREAD_GRAYSCALE) # 自适应阈值 - 均值 thresh_mean = cv2.adaptiveThreshold( img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2 ) # 自适应阈值 - 高斯 thresh_gaussian = cv2.adaptiveThreshold( img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) # 显示对比 cv2.imshow('Original', img) cv2.imshow('Mean', thresh_mean) cv2.imshow('Gaussian', thresh_gaussian) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 参数说明 ```python cv2.adaptiveThreshold( src, # 输入灰度图像 maxValue, # 满足条件的最大值 adaptiveMethod, # 自适应方法 thresholdType, # 阈值类型 blockSize, # 邻域大小(奇数) C # 从均值或加权均值减去的常数 ) # adaptiveMethod: # ADAPTIVE_THRESH_MEAN_C - 邻域均值 # ADAPTIVE_THRESH_GAUSSIAN_C - 邻域高斯加权和 # thresholdType: # THRESH_BINARY - 二值化 # THRESH_BINARY_INV - 反向二值化 # blockSize: 邻域大小(3, 5, 7, 9, ...) # C: 偏移值,用于微调 ``` ### 自适应阈值 vs 固定阈值 ```python import cv2 img = cv2.imread('uneven_lighting.jpg', cv2.IMREAD_GRAYSCALE) # 固定阈值 ret, thresh_fixed = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 自适应阈值 thresh_adaptive = cv2.adaptiveThreshold( img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 3 ) cv2.imshow('Original', img) cv2.imshow('Fixed Threshold', thresh_fixed) cv2.imshow('Adaptive Threshold', thresh_adaptive) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:文档扫描预处理 ```python import cv2 import numpy as np def preprocess_document(image_path): """文档扫描预处理""" # 读取图像 img = cv2.imread(image_path) # 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 降噪 denoised = cv2.fastNlMeansDenoising(gray) # 自适应阈值 binary = cv2.adaptiveThreshold( denoised, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 10 ) # 形态学操作 kernel = np.ones((2, 2), np.uint8) processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return processed # 使用 result = preprocess_document('document.jpg') cv2.imshow('Processed Document', result) cv2.waitKey(0) cv2.destroyAllWindows() ```
## Otsu阈值(大津法) ### Otsu原理 Otsu方法是一种自动确定最佳阈值的算法,通过最大化类间方差来找到最优阈值。 ### 基本用法 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 全局阈值 ret1, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # Otsu阈值 ret2, thresh2 = cv2.threshold( img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU ) print(f"Otsu最优阈值: {ret2}") cv2.imshow('Original', img) cv2.imshow('Global Threshold (127)', thresh1) cv2.imshow(f'Otsu Threshold ({ret2:.0f})', thresh2) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Otsu适用场景 Otsu方法适用于具有双峰直方图的图像(前景和背景有明显区别)。 | 场景 | 适用性 | 说明 | |-----|-------|------| | 文档扫描 | ✅ 适合 | 黑白分明 | | 医学图像 | ✅ 适合 | 组织对比明显 | | 自然场景 | ❌ 不适合 | 直方图复杂 | | 光照不均 | ❌ 不适合 | 多峰值直方图 | ### Otsu + 高斯滤波(去噪) ```python import cv2 img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE) # 高斯滤波去噪 blur = cv2.GaussianBlur(img, (5, 5), 0) # 应用Otsu阈值 ret, otsu = cv2.threshold( blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU ) print(f"Otsu阈值: {ret}") cv2.imshow('Original', img) cv2.imshow('Otsu', otsu) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Otsu + 遮罩 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 创建遮罩(只对中央区域应用Otsu) mask = np.zeros(img.shape[:2], np.uint8) h, w = img.shape mask[h//4:3*h//4, w//4:3*w//4] = 255 # 应用遮罩的Otsu ret, thresh = cv2.threshold( img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU, mask ) cv2.imshow('Mask', mask) cv2.imshow('Otsu with Mask', thresh) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 阈值方法选择指南 ``` 图像特征分析: 直方图双峰明显 → Otsu阈值 直方图单峰 → 固定阈值 光照均匀 → 固定阈值 光照不均 → 自适应阈值 噪声较多 → 先滤波再阈值 文档处理 → 自适应阈值 医学图像 → Otsu阈值 ```
图像滤波与平滑
## 二维卷积(图像滤波) ### cv2.filter2D() - 自定义卷积核 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 创建5x5平均卷积核 kernel = np.ones((5, 5), np.float32) / 25 # 应用卷积 dst = cv2.filter2D(img, -1, kernel) cv2.imshow('Original', img) cv2.imshow('Filtered', dst) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 常用卷积核 ```python import numpy as np # 1. 锐化核 sharpen_kernel = np.array([ [0, -1, 0], [-1, 5, -1], [0, -1, 0] ], np.float32) # 2. 边缘检测核 edge_kernel = np.array([ [-1, -1, -1], [-1, 8, -1], [-1, -1, -1] ], np.float32) # 3. 拉普拉斯算子 laplacian_kernel = np.array([ [0, 1, 0], [1, -4, 1], [0, 1, 0] ], np.float32) # 4. 方向性边缘检测(Sobel X方向) sobel_x = np.array([ [-1, 0, 1], [-2, 0, 2], [-1, 0, 1] ], np.float32) # 5. 方向性边缘检测(Sobel Y方向) sobel_y = np.array([ [-1, -2, -1], [0, 0, 0], [1, 2, 1] ], np.float32) # 使用示例 img = cv2.imread('image.jpg') sharpened = cv2.filter2D(img, -1, sharpen_kernel) edges = cv2.filter2D(img, -1, edge_kernel) ``` ### 卷积核设计原理 ``` 卷积操作原理: ┌─────────────────────────────────────────┐ │ │ │ 原图像 卷积核 输出图像 │ │ ┌───┐ ┌───┐ ┌───┐ │ │ │1 2│ × │1 1│ = │ ? │ │ │ │3 4│ │1 1│ │ │ │ │ └───┘ └───┘ └───┘ │ │ │ │ 计算: │ │ ? = 1×1 + 2×1 + 3×1 + 4×1 = 10 │ │ │ └─────────────────────────────────────────┘ 卷积核大小通常为奇数 (3×3, 5×5, 7×7) 中心像素是参考点 ``` ### 实用示例:浮雕效果 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 浮雕效果卷积核 emboss_kernel = np.array([ [-2, -1, 0], [-1, 1, 1], [0, 1, 2] ], np.float32) # 应用卷积 embossed = cv2.filter2D(img, -1, emboss_kernel) # 添加灰度值使效果更明显 embossed = cv2.add(embossed, np.array([128, 128, 128])) cv2.imshow('Emboss Effect', embossed) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 图像模糊(平滑) ### 1. 平均模糊 - cv2.blur() ```python import cv2 img = cv2.imread('image.jpg') # 平均模糊 blur = cv2.blur(img, (5, 5)) # 参数说明 # src: 输入图像 # ksize: 核大小 (width, height),必须是正奇数 cv2.imshow('Original', img) cv2.imshow('Blurred', blur) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 2. 高斯模糊 - cv2.GaussianBlur() ```python import cv2 img = cv2.imread('image.jpg') # 高斯模糊 # (5, 5) 是核大小 # 0 是标准差(0表示自动计算) blur = cv2.GaussianBlur(img, (5, 5), 0) # 分别指定X和Y方向的标准差 blur = cv2.GaussianBlur(img, (5, 5), sigmaX=1, sigmaY=1) # 高斯模糊核示例 # 5×5 高斯核 (σ=1) """ [[ 1, 4, 7, 4, 1], [ 4, 16, 26, 16, 4], [ 7, 26, 41, 26, 7], [ 4, 16, 26, 16, 4], [ 1, 4, 7, 4, 1]] / 273 """ cv2.imshow('Gaussian Blur', blur) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 3. 中值模糊 - cv2.medianBlur() ```python import cv2 img = cv2.imread('noisy_image.jpg') # 中值模糊 # 核大小必须是大于1的奇数 median = cv2.medianBlur(img, 5) # 中值模糊对椒盐噪声特别有效 # 它取邻域像素的中值,而不是平均值 # 添加椒盐噪声示例 import numpy as np def add_salt_pepper_noise(image, prob=0.01): noisy = image.copy() # 盐噪声(白点) salt_mask = np.random.random(image.shape[:2]) < prob/2 noisy[salt_mask] = 255 # 椒噪声(黑点) pepper_mask = np.random.random(image.shape[:2]) < prob/2 noisy[pepper_mask] = 0 return noisy noisy = add_salt_pepper_noise(img) denoised = cv2.medianBlur(noisy, 3) cv2.imshow('Noisy', noisy) cv2.imshow('Median Filter', denoised) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 模糊方法对比 | 方法 | 特点 | 适用场景 | 速度 | |-----|------|----------|------| | 平均模糊 | 简单快速,但效果粗糙 | 快速预览 | 最快 | | 高斯模糊 | 平滑自然,保留边缘 | 一般降噪 | 快 | | 中值模糊 | 去除椒盐噪声 | 噪声图像 | 较慢 | | 双边滤波 | 保持边缘清晰 | 保边去噪 | 最慢 | ### 实用示例:多尺度高斯模糊 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 不同尺度的高斯模糊 blur1 = cv2.GaussianBlur(img, (3, 3), 0) blur2 = cv2.GaussianBlur(img, (7, 7), 0) blur3 = cv2.GaussianBlur(img, (15, 15), 0) # 创建对比图 comparison = np.hstack((img, blur1, blur2, blur3)) cv2.imshow('Gaussian Blur Comparison', comparison) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 双边滤波 ### 双边滤波原理 双边滤波在去噪的同时能有效保持边缘清晰,这是因为: 1. **空间高斯函数**:考虑像素的空间距离 2. **强度高斯函数**:考虑像素值的相似度 只有空间上接近且值相似的像素才会被用来模糊中心像素。 ### cv2.bilateralFilter() ```python import cv2 img = cv2.imread('image.jpg') # 双边滤波 # d: 滤波过程中每个像素邻域的直径 # sigmaColor: 颜色空间的标准差 # sigmaSpace: 坐标空间的标准差 bilateral = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75) # 参数说明: # d: 推荐值-1(自动计算)或5-9 # sigmaColor: 值越大,颜色差异大的像素也会互相影响 # sigmaSpace: 值越大,距离远的像素也会互相影响 cv2.imshow('Original', img) cv2.imshow('Bilateral Filter', bilateral) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 双边滤波 vs 高斯模糊 ```python import cv2 img = cv2.imread('portrait.jpg') # 高斯模糊 gaussian = cv2.GaussianBlur(img, (9, 9), 0) # 双边滤波 bilateral = cv2.bilateralFilter(img, 9, 75, 75) # 对比显示 # 高斯模糊会模糊边缘 # 双边滤波保持边缘清晰 comparison = np.hstack((img, gaussian, bilateral)) cv2.imshow('Comparison', comparison) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:卡通效果 ```python import cv2 def cartoon_effect(image_path): """使用双边滤波创建卡通效果""" img = cv2.imread(image_path) # 1. 双边滤波 # 重复应用多次效果更好 filtered = cv2.bilateralFilter(img, 15, 80, 80) filtered = cv2.bilateralFilter(filtered, 15, 80, 80) filtered = cv2.bilateralFilter(filtered, 15, 80, 80) # 2. 转灰度 gray = cv2.cvtColor(filtered, cv2.COLOR_BGR2GRAY) # 3. 边缘检测 edges = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 2 ) # 4. 转换为彩色 edges = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR) # 5. 与滤波图像相与 cartoon = cv2.bitwise_and(filtered, edges) return cartoon cartoon = cartoon_effect('portrait.jpg') cv2.imshow('Cartoon Effect', cartoon) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 双边滤波参数选择指南 ``` d(直径): 小 (3-5): 细节保留,计算快 中 (7-9): 平衡效果 大 (11+): 平滑效果好,但慢 sigmaColor: 小 (<50): 只有非常相似的像素才互相影响 中 (50-100): 适度平滑 大 (>100): 颜色差异大的像素也互相影响 sigmaSpace: 小 (<50): 只有空间距离近的像素才互相影响 中 (50-100): 适度空间影响 大 (>100): 空间距离远的像素也互相影响 ```
## 图像锐化 ### 锐化原理 锐化是边缘增强的反操作,通过增强边缘对比度使图像看起来更清晰。 ### 基本锐化方法 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') # 方法1: 使用卷积核 sharpen_kernel = np.array([ [0, -1, 0], [-1, 5, -1], [0, -1, 0] ], np.float32) sharpened = cv2.filter2D(img, -1, sharpen_kernel) # 方法2: 高斯差分 # 创建两个不同模糊程度的图像 blur1 = cv2.GaussianBlur(img, (3, 3), 0) blur2 = cv2.GaussianBlur(img, (9, 9), 0) # 相减得到边缘 edges = cv2.subtract(blur2, blur1) # 添加到原图 sharpened2 = cv2.add(img, edges) cv2.imshow('Original', img) cv2.imshow('Sharpened (Kernel)', sharpened) cv2.imshow('Sharpened (DoG)', sharpened2) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Unsharp Masking(USM锐化) ```python import cv2 import numpy as np def unsharp_mask(image, sigma=1.0, strength=1.5): """USM锐化算法""" # 1. 高斯模糊 blurred = cv2.GaussianBlur(image, (0, 0), sigma) # 2. 计算差值(掩模) mask = cv2.subtract(image, blurred) # 3. 按强度添加到原图 sharpened = cv2.addWeighted(image, 1, mask, strength, 0) return sharpened img = cv2.imread('image.jpg') # 不同强度的锐化 usm1 = unsharp_mask(img, sigma=1.0, strength=0.5) usm2 = unsharp_mask(img, sigma=1.0, strength=1.0) usm3 = unsharp_mask(img, sigma=1.0, strength=2.0) comparison = np.hstack((img, usm1, usm2, usm3)) cv2.imshow('Unsharp Masking', comparison) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 锐化技巧 ```python import cv2 # 1. Lab颜色空间锐化(只处理亮度通道) img = cv2.imread('image.jpg') lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) # 只对L通道锐化 l_sharpen = cv2.filter2D(l, -1, np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])) # 合并通道 sharpened_lab = cv2.merge([l_sharpen, a, b]) sharpened = cv2.cvtColor(sharpened_lab, cv2.COLOR_LAB2BGR) cv2.imshow('Lab Sharpening', sharpened) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 去噪与锐化组合 ```python import cv2 def enhance_image(img): """图像增强:先去噪再锐化""" # 1. 双边滤波去噪 denoised = cv2.bilateralFilter(img, 9, 75, 75) # 2. USM锐化 sharpened = unsharp_mask(denoised, sigma=1.5, strength=1.2) return sharpened img = cv2.imread('noisy_image.jpg') enhanced = enhance_image(img) cv2.imshow('Enhanced', enhanced) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 滤波操作总结 ``` 滤波操作选择指南: 目的:去噪 ├── 均匀噪声 → 高斯模糊 ├── 椒盐噪声 → 中值模糊 └── 保边缘去噪 → 双边滤波 目的:平滑 ├── 快速预览 → 平均模糊 ├── 自然效果 → 高斯模糊 └── 特殊效果 → 双边滤波 目的:增强 ├── 锐化 → 锐化核或USM ├── 边缘增强 → 边缘检测 └── 细节增强 → 高通滤波 ```
形态学操作
## 形态学操作基础 ### 什么是形态学操作? 形态学操作是基于形状的图像处理方法,主要用于处理二值图像。基本操作包括腐蚀、膨胀、开运算、闭运算等。 ### 结构元素(核) ```python import cv2 import numpy as np # 创建结构元素 # 矩形核 kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) """ [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] """ # 椭圆核 kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) """ [[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0]] """ # 十字核 kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5)) """ [[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]] """ # 自定义核 kernel_custom = np.ones((3, 3), np.uint8) ``` ### 结构元素形状对比 | 形状 | 特点 | 适用场景 | |-----|------|----------| | 矩形 (MORPH_RECT) | 全方向均匀 | 一般用途 | | 椭圆 (MORPH_ELLIPSE) | 圆形平滑 | 细粒度处理 | | 十字 (MORPH_CROSS) | 十字形状 | 保持水平/垂直线条 | ### 形态学操作应用场景 ``` 形态学操作典型应用: ┌─────────────────────────────────────────┐ │ │ │ 降噪:去除小噪声点 │ │ 分离:分离连接的对象 │ │ 填充:填充对象中的小孔 │ │ 边界:提取对象边界 │ │ 骨架:提取对象骨架 │ │ │ └─────────────────────────────────────────┘ ```
## 腐蚀与膨胀 ### 腐蚀 (Erosion) ```python import cv2 import numpy as np # 读取图像并转为二值 img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) # 腐蚀操作 kernel = np.ones((5, 5), np.uint8) erosion = cv2.erode(img, kernel, iterations=1) # 多次腐蚀 erosion_3 = cv2.erode(img, kernel, iterations=3) cv2.imshow('Original', img) cv2.imshow('Erosion (1 iter)', erosion) cv2.imshow('Erosion (3 iter)', erosion_3) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 膨胀 (Dilation) ```python import cv2 import numpy as np img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) # 膨胀操作 kernel = np.ones((5, 5), np.uint8) dilation = cv2.dilate(img, kernel, iterations=1) # 多次膨胀 dilation_3 = cv2.dilate(img, kernel, iterations=3) cv2.imshow('Original', img) cv2.imshow('Dilation (1 iter)', dilation) cv2.imshow('Dilation (3 iter)', dilation_3) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 腐蚀与膨胀原理 ``` 腐蚀操作(去除白色区域): 原图: 腐蚀后: ████ ░██░ ███ → ░██░ ████ ░██░ 效果:白色区域缩小,黑色区域扩大 膨胀操作(扩大白色区域): 原图: 膨胀后: ░██░ ████ ░██░ → █████ ░██░ ████ 效果:白色区域扩大,黑色区域缩小 ``` ### 实用示例:去除小噪声 ```python import cv2 import numpy as np def remove_small_noise(binary_img, min_size=5): """去除二值图像中的小噪声点""" kernel = np.ones((min_size, min_size), np.uint8) # 先腐蚀再膨胀 # 腐蚀会去除小点(如果核大小大于噪声点) eroded = cv2.erode(binary_img, kernel, iterations=1) # 膨胀恢复主要对象大小 dilated = cv2.dilate(eroded, kernel, iterations=1) return dilated # 使用 binary_img = cv2.imread('noisy_binary.jpg', cv2.IMREAD_GRAYSCALE) cleaned = remove_small_noise(binary_img) cv2.imshow('Noisy', binary_img) cv2.imshow('Cleaned', cleaned) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 开运算与闭运算 ### 开运算(Opening) 开运算是先腐蚀后膨胀,用于去除小对象、分离连接的对象。 ```python import cv2 import numpy as np img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((5, 5), np.uint8) # 开运算:先腐蚀后膨胀 opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 等价于: # opening = cv2.dilate(cv2.erode(img, kernel), kernel) cv2.imshow('Original', img) cv2.imshow('Opening', opening) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 闭运算(Closing) 闭运算是先膨胀后腐蚀,用于填充小孔、连接邻近对象。 ```python import cv2 import numpy as np img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((5, 5), np.uint8) # 闭运算:先膨胀后腐蚀 closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # 等价于: # closing = cv2.erode(cv2.dilate(img, kernel), kernel) cv2.imshow('Original', img) cv2.imshow('Closing', closing) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 形态学操作对比 ```python import cv2 import numpy as np img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((5, 5), np.uint8) # 各种形态学操作 erosion = cv2.erode(img, kernel, iterations=1) dilation = cv2.dilate(img, kernel, iterations=1) 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) titles = ['Original', 'Erosion', 'Dilation', 'Opening', 'Closing'] images = [img, erosion, dilation, opening, closing] for i in range(5): cv2.imshow(titles[i], images[i]) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:车牌预处理 ```python import cv2 import numpy as np def preprocess_license_plate(image): """车牌图像预处理""" # 转灰度 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 形态学操作 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 闭运算(连接字符) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) # 开运算(去除小噪声) opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel) return opened img = cv2.imread('license_plate.jpg') processed = preprocess_license_plate(img) cv2.imshow('Processed', processed) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 形态学梯度 ### 形态学梯度 梯度是膨胀与腐蚀的差值,用于突出边缘。 ```python import cv2 import numpy as np img = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((5, 5), np.uint8) # 形态学梯度 = 膨胀 - 腐蚀 gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 等价于: # gradient = cv2.dilate(img, kernel) - cv2.erode(img, kernel) cv2.imshow('Original', img) cv2.imshow('Gradient', gradient) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 顶帽 (Top Hat) 顶帽是原图与开运算的差值,突出比邻域亮的区域。 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((15, 15), np.uint8) # 顶帽 = 原图 - 开运算 tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) cv2.imshow('Original', img) cv2.imshow('Top Hat', tophat) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 黑帽 (Black Hat) 黑帽是闭运算与原图的差值,突出比邻域暗的区域。 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) kernel = np.ones((15, 15), np.uint8) # 黑帽 = 闭运算 - 原图 blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) cv2.imshow('Original', img) cv2.imshow('Black Hat', blackhat) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 形态学操作总结 | 操作 | 公式 | 效果 | 应用 | |-----|------|------|------| | 腐蚀 | $A \ominus B$ | 白色区域缩小 | 去除小噪声 | | 膨胀 | $A \oplus B$ | 白色区域扩大 | 填充小孔 | | 开运算 | $A \circ B$ | 去除小对象 | 降噪、分离 | | 闭运算 | $A \cdot B$ | 填充小孔 | 连接对象 | | 梯度 | $\oplus - \ominus$ | 突出边缘 | 边缘检测 | | 顶帽 | $A - (A \circ B)$ | 突出亮细节 | 增强亮区 | | 黑帽 | $(A \cdot B) - A$ | 突出暗细节 | 增强暗区 | ### 实用示例:文档图像增强 ```python import cv2 import numpy as np def enhance_document(image_path): """文档图像增强""" img = cv2.imread(image_path) # 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 形态学操作 kernel = np.ones((2, 2), np.uint8) # 去除小噪声 cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 填充字符内部 filled = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel) return filled enhanced = enhance_document('document.jpg') cv2.imshow('Enhanced Document', enhanced) cv2.waitKey(0) cv2.destroyAllWindows() ```
边缘检测
## Sobel算子 ### Sobel算子原理 Sobel算子是一种离散微分算子,用于计算图像灰度函数的近似梯度。 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # Sobel X方向(检测垂直边缘) sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # Sobel Y方向(检测水平边缘) sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 转换回uint8 sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.convertScaleAbs(sobely) # 合并X和Y方向 sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0) cv2.imshow('Original', img) cv2.imshow('Sobel X', sobelx) cv2.imshow('Sobel Y', sobely) cv2.imshow('Sobel XY', sobelxy) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Sobel算子卷积核 ``` X方向核(检测垂直边缘): [-1 0 1] [-2 0 2] [-1 0 1] Y方向核(检测水平边缘): [-1 -2 -1] [ 0 0 0] [ 1 2 1] ``` ### 不同核大小的Sobel ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 不同核大小 sobel3 = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3) sobel5 = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=5) # 转换回uint8 sobel3 = cv2.convertScaleAbs(sobel3) sobel5 = cv2.convertScaleAbs(sobel5) cv2.imshow('Sobel 3x3', sobel3) cv2.imshow('Sobel 5x5', sobel5) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Scharr算子 Scharr算子是Sobel的改进版本,提供更好的旋转对称性。 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # Scharr算子(核大小固定为3) scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0) scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1) # 转换 scharrx = cv2.convertScaleAbs(scharrx) scharry = cv2.convertScaleAbs(scharry) # 合并 scharr = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0) cv2.imshow('Scharr', scharr) cv2.waitKey(0) cv2.destroyAllWindows() ```
## Laplacian算子 ### Laplacian算子原理 Laplacian算子计算二阶导数,对噪声敏感,但能检测各个方向的边缘。 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 先去噪 blur = cv2.GaussianBlur(img, (3, 3), 0) # Laplacian算子 laplacian = cv2.Laplacian(blur, cv2.CV_64F, ksize=3) # 转换回uint8 laplacian = cv2.convertScaleAbs(laplacian) cv2.imshow('Original', img) cv2.imshow('Laplacian', laplacian) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Laplacian卷积核 ``` 标准Laplacian核: [ 0 1 0] [ 1 -4 1] [ 0 1 0] 改进Laplacian核: [ 1 1 1] [ 1 -8 1] [ 1 1 1] ``` ### Laplacian与Sobel对比 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) blur = cv2.GaussianBlur(img, (3, 3), 0) # Laplacian laplacian = cv2.Laplacian(blur, cv2.CV_64F) laplacian = cv2.convertScaleAbs(laplacian) # Sobel组合 sobelx = cv2.Sobel(blur, cv2.CV_64F, 1, 0) sobely = cv2.Sobel(blur, cv2.CV_64F, 0, 1) sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.convertScaleAbs(sobely) sobel = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0) cv2.imshow('Laplacian', laplacian) cv2.imshow('Sobel', sobel) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 边缘检测算子对比 | 算子 | 优点 | 缺点 | 适用场景 | |-----|------|------|----------| | Sobel | 简单快速,抗噪性好 | 只能检测两个方向 | 一般边缘检测 | | Scharr | 精度高,旋转对称好 | 计算量大 | 高精度要求 | | Laplacian | 各向同性,检测所有方向 | 对噪声敏感 | 预处理后的图像 |
## Canny边缘检测 ### Canny算法原理 Canny边缘检测是最流行的边缘检测算法,包含以下步骤: 1. 高斯滤波(去噪) 2. 计算梯度幅值和方向 3. 非极大值抑制(细化边缘) 4. 双阈值检测和边缘连接 ### cv2.Canny() ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # Canny边缘检测 # threshold1: 低阈值 # threshold2: 高阈值 edges = cv2.Canny(img, 100, 200) cv2.imshow('Original', img) cv2.imshow('Canny Edges', edges) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Canny参数详解 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 不同参数组合 edges1 = cv2.Canny(img, 50, 150) # 低阈值,检测更多边缘 edges2 = cv2.Canny(img, 100, 200) # 标准参数 edges3 = cv2.Canny(img, 150, 250) # 高阈值,只检测强边缘 cv2.imshow('Low Thresholds', edges1) cv2.imshow('Standard', edges2) cv2.imshow('High Thresholds', edges3) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Canny参数选择指南 ``` 双阈值选择原则: 高阈值 : 低阈值 ≈ 2:1 到 3:1 低阈值:控制边缘连接 太低:噪声也被连接 太高:边缘断裂 高阈值:控制强边缘检测 太低:太多弱边缘 太高:丢失重要边缘 推荐值: 高对比度图像:150, 300 标准图像:100, 200 低对比度图像:50, 150 ``` ### 自动Canny参数 ```python import cv2 import numpy as np def auto_canny(image, sigma=0.33): """自动计算Canny阈值""" # 计算图像中位数 v = np.median(image) # 计算阈值 lower = int(max(0, (1.0 - sigma) * v)) upper = int(min(255, (1.0 + sigma) * v)) # 应用Canny edged = cv2.Canny(image, lower, upper) return edged img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) edges = auto_canny(img) cv2.imshow('Auto Canny', edges) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Canny预处理 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 1. 降噪 blur = cv2.GaussianBlur(img, (5, 5), 0) # 2. 应用Canny edges = cv2.Canny(blur, 50, 150) # 3. 形态学操作(可选) kernel = np.ones((2, 2), np.uint8) dilated = cv2.dilate(edges, kernel, iterations=1) cv2.imshow('Processed Canny', dilated) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 边缘检测应用 ### 轮廓检测 ```python import cv2 img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # Canny边缘检测 edges = cv2.Canny(img, 100, 200) # 查找轮廓 contours, hierarchy = cv2.findContours( edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) # 绘制轮廓 result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) cv2.drawContours(result, contours, -1, (0, 255, 0), 2) cv2.imshow('Contours', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 边缘增强 ```python import cv2 import numpy as np img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 获取边缘 edges = cv2.Canny(img, 100, 200) # 反转边缘 edges_inv = cv2.bitwise_not(edges) # 与原图相加 enhanced = cv2.add(img, edges_inv) cv2.imshow('Edge Enhanced', enhanced) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 草图效果 ```python import cv2 def pencil_sketch(image_path): """创建素描效果""" img = cv2.imread(image_path) # 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊 blur = cv2.GaussianBlur(gray, (21, 21), 0) # Canny边缘检测 edges = cv2.Canny(blur, 30, 60) # 反转边缘 edges_inv = cv2.bitwise_not(edges) # 与模糊图混合 sketch = cv2.divide(gray, blur, scale=256) return edges_inv, sketch edges, sketch = pencil_sketch('portrait.jpg') cv2.imshow('Edges', edges) cv2.imshow('Sketch', sketch) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 边缘检测流程选择 ``` 边缘检测方法选择: 场景特点 推荐方法 说明 ───────────────────────────────────── 快速预览 Sobel 简单快速 高精度要求 Scharr 旋转对称性好 各方向边缘 Laplacian 各向同性 复杂场景 Canny 最全面 预处理建议: 有噪声 → 先高斯滤波 低对比度 → 先直方图均衡化 有纹理 → 考虑其他方法 ```
几何变换
## 图像缩放 ### cv2.resize() 基本用法 ```python import cv2 img = cv2.imread('image.jpg') # 方法1: 指定宽高 resized = cv2.resize(img, (640, 480)) # (width, height) # 方法2: 按比例缩放 height, width = img.shape[:2] resized = cv2.resize(img, (int(width*0.5), int(height*0.5))) # 方法3: 使用fx, fy resized = cv2.resize(img, None, fx=0.5, fy=0.5) cv2.imshow('Original', img) cv2.imshow('Resized', resized) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 缩放插值方法 ```python import cv2 img = cv2.imread('image.jpg') # 不同插值方法 nearest = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_NEAREST) linear = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LINEAR) cubic = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC) lanczos = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LANCZOS4) cv2.imshow('Nearest', nearest) cv2.imshow('Linear', linear) cv2.imshow('Cubic', cubic) cv2.imshow('Lanczos', lanczos) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 插值方法对比 | 方法 | 速度 | 质量 | 适用场景 | |-----|------|------|----------| | INTER_NEAREST | 最快 | 最差 | 快速预览、像素艺术 | | INTER_LINEAR | 快 | 中等 | 一般缩放(默认) | | INTER_CUBIC | 慢 | 好 | 放大图像 | | INTER_LANCZOS4 | 最慢 | 最好 | 高质量缩放 | ### 保持宽高比缩放 ```python import cv2 def resize_with_aspect_ratio(image, width=None, height=None): """保持宽高比缩放""" h, w = image.shape[:2] if width is None and height is None: return image if width is None: ratio = height / h dim = (int(w * ratio), height) else: ratio = width / w dim = (width, int(h * ratio)) resized = cv2.resize(image, dim) return resized img = cv2.imread('image.jpg') resized = resize_with_aspect_ratio(img, width=640) cv2.imshow('Resized', resized) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 图像翻转 ```python import cv2 img = cv2.imread('image.jpg') # 水平翻转(镜像) flip_horizontal = cv2.flip(img, 1) # 垂直翻转 flip_vertical = cv2.flip(img, 0) # 同时水平和垂直翻转 flip_both = cv2.flip(img, -1) cv2.imshow('Horizontal', flip_horizontal) cv2.imshow('Vertical', flip_vertical) cv2.imshow('Both', flip_both) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 平移与旋转 ### 图像平移 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') h, w = img.shape[:2] # 平移矩阵:[[1, 0, tx], [0, 1, ty]] tx, ty = 100, 50 # x方向平移100,y方向平移50 M = np.array([[1, 0, tx], [0, 1, ty], [0, 0, 1]], dtype=np.float32) # 应用变换 translated = cv2.warpAffine(img, M[:2], (w, h)) cv2.imshow('Translated', translated) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 图像旋转 ```python import cv2 img = cv2.imread('image.jpg') h, w = img.shape[:2] # 获取旋转矩阵 # center: 旋转中心 # angle: 旋转角度(度) # scale: 缩放因子 M = cv2.getRotationMatrix2D((w/2, h/2), 45, 1) # 应用旋转 rotated = cv2.warpAffine(img, M, (w, h)) cv2.imshow('Rotated', rotated) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 旋转而不裁剪 ```python import cv2 import numpy as np def rotate_without_crop(image, angle): """旋转图像而不裁剪""" h, w = image.shape[:2] # 获取旋转矩阵 center = (w / 2, h / 2) M = cv2.getRotationMatrix2D(center, angle, 1.0) # 计算新图像大小 cos = np.abs(M[0, 0]) sin = np.abs(M[0, 1]) new_w = int((h * sin) + (w * cos)) new_h = int((h * cos) + (w * sin)) # 调整平移量 M[0, 2] += (new_w / 2) - center[0] M[1, 2] += (new_h / 2) - center[1] # 应用旋转 rotated = cv2.warpAffine(image, M, (new_w, new_h)) return rotated img = cv2.imread('image.jpg') rotated = rotate_without_crop(img, 30) cv2.imshow('Rotated Without Crop', rotated) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 组合变换 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') h, w = img.shape[:2] # 平移 M1 = np.array([[1, 0, 50], [0, 1, 50]], dtype=np.float32) # 旋转 M2 = cv2.getRotationMatrix2D((w/2, h/2), 30, 1) # 组合变换(先旋转再平移) M = np.vstack([M2, [0, 0, 1]]) @ np.vstack([M1, [0, 0, 1]]) M = M[:2] result = cv2.warpAffine(img, M, (w, h)) cv2.imshow('Combined Transform', result) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 仿射变换 ### 什么是仿射变换? 仿射变换是一种保持直线平行性和共线性的变换,包括旋转、平移、缩放、剪切等。 ### 三点法确定仿射变换 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') h, w = img.shape[:2] # 原图像中的三个点 pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 目标图像中的三个点 pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # 计算仿射变换矩阵 M = cv2.getAffineTransform(pts1, pts2) # 应用仿射变换 result = cv2.warpAffine(img, M, (w, h)) cv2.imshow('Affine Transform', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:图像校正 ```python import cv2 import numpy as np def deskew(image): """校正倾斜的文档图像""" # 转灰度 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 二值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 获取所有非零点坐标 coords = np.column_stack(np.where(binary > 0)) # 获取最小外接矩形的角度 angle = cv2.minAreaRect(coords)[-1] # 校正角度 if angle < -45: angle = -(90 + angle) else: angle = -angle # 旋转图像 (h, w) = image.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) return rotated img = cv2.imread('skewed_document.jpg') deskewed = deskew(img) cv2.imshow('Original', img) cv2.imshow('Deskewed', deskewed) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 剪切变换 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') h, w = img.shape[:2] # 剪切矩阵 M_shear = np.array([ [1, 0.5, 0], # 水平剪切 [0, 1, 0], [0, 0, 1] ], dtype=np.float32) sheared = cv2.warpAffine(img, M_shear[:2], (int(w*1.5), h)) cv2.imshow('Sheared', sheared) cv2.waitKey(0) cv2.destroyAllWindows() ```
## 透视变换 ### 什么是透视变换? 透视变换可以将图像中的四边形区域变换为另一个四边形,常用于文档扫描、车牌校正等。 ### 四点法确定透视变换 ```python import cv2 import numpy as np img = cv2.imread('document.jpg') h, w = img.shape[:2] # 原图像中的四个点 pts1 = np.float32([[100, 100], [400, 100], [100, 400], [400, 400]]) # 目标图像中的四个点 pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]]) # 计算透视变换矩阵 M = cv2.getPerspectiveTransform(pts1, pts2) # 应用透视变换 result = cv2.warpPerspective(img, M, (300, 300)) cv2.imshow('Perspective Transform', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 实用示例:文档扫描 ```python import cv2 import numpy as np def scan_document(image_path): """自动扫描并校正文档""" img = cv2.imread(image_path) # 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊 blur = cv2.GaussianBlur(gray, (5, 5), 0) # Canny边缘检测 edges = cv2.Canny(blur, 75, 200) # 查找轮廓 contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 找到最大的四边形轮廓 max_area = 0 best_cnt = None for cnt in contours: area = cv2.contourArea(cnt) if area > max_area: # 多边形近似 peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) if len(approx) == 4: max_area = area best_cnt = approx if best_cnt is None: print("未找到文档边界") return img # 获取四个顶点 pts = best_cnt.reshape(4, 2).astype(np.float32) # 排序点(左上、右上、右下、左下) rect = np.zeros((4, 2), dtype=np.float32) sum_pts = pts.sum(axis=1) rect[0] = pts[np.argmin(sum_pts)] # 左上 rect[2] = pts[np.argmax(sum_pts)] # 右下 diff_pts = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff_pts)] # 右上 rect[3] = pts[np.argmax(diff_pts)] # 左下 # 计算目标宽高 width_a = np.linalg.norm(rect[2] - rect[3]) width_b = np.linalg.norm(rect[1] - rect[0]) max_width = max(int(width_a), int(width_b)) height_a = np.linalg.norm(rect[1] - rect[2]) height_b = np.linalg.norm(rect[0] - rect[3]) max_height = max(int(height_a), int(height_b)) # 目标点 dst = np.array([ [0, 0], [max_width - 1, 0], [max_width - 1, max_height - 1], [0, max_height - 1] ], dtype=np.float32) # 透视变换 M = cv2.getPerspectiveTransform(rect, dst) result = cv2.warpPerspective(img, M, (max_width, max_height)) return result scanned = scan_document('document.jpg') cv2.imshow('Scanned Document', scanned) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 图像配准 ```python import cv2 import numpy as np def align_images(img1, img2): """将img2对齐到img1""" # 转灰度 gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 检测特征点 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(gray1, None) kp2, des2 = orb.detectAndCompute(gray2, None) # 匹配特征 matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = matcher.match(des1, des2) # 提取匹配点 src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]) dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]) # 计算仿射变换 M, _ = cv2.estimateAffinePartial2D(dst_pts, src_pts) if M is not None: # 应用变换 h, w = img1.shape[:2] aligned = cv2.warpAffine(img2, M, (w, h)) return aligned return img2 img1 = cv2.imread('reference.jpg') img2 = cv2.imread('target.jpg') aligned = align_images(img1, img2) cv2.imshow('Aligned', aligned) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 几何变换总结 | 变换类型 | 自由度 | 保持性质 | 应用场景 | |---------|--------|----------|----------| | 平移 | 2 | 平行性、角度 | 图像对齐 | | 旋转 | 1 | 距离、角度 | 图像校正 | | 缩放 | 2 | 角度、比例 | 图像调整大小 | | 仿射 | 6 | 平行性 | 图像校正、配准 | | 透视 | 8 | 直线性 | 文档扫描、3D重建 |
视频处理
## 读取视频 ### cv2.VideoCapture() ```python import cv2 # 打开视频文件 cap = cv2.VideoCapture('video.mp4') # 检查是否成功打开 if not cap.isOpened(): print("无法打开视频文件") exit() while True: # 逐帧读取 ret, frame = cap.read() # ret表示是否成功读取帧 if not ret: print("视频结束") break # 处理帧 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 显示帧 cv2.imshow('Frame', frame) # 按'q'退出 if cv2.waitKey(25) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows() ``` ### 使用摄像头 ```python import cv2 # 打开默认摄像头 cap = cv2.VideoCapture(0) # 检查是否成功打开 if not cap.isOpened(): print("无法打开摄像头") exit() while True: ret, frame = cap.read() if not ret: print("无法获取帧") break # 水平翻转(镜像效果) frame = cv2.flip(frame, 1) cv2.imshow('Camera', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ``` ### 设置摄像头参数 ```python import cv2 cap = cv2.VideoCapture(0) # 设置分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 设置帧率 cap.set(cv2.CAP_PROP_FPS, 30) # 设置亮度 cap.set(cv2.CAP_PROP_BRIGHTNESS, 0.5) print(f"分辨率: {int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))}x{int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))}") print(f"帧率: {cap.get(cv2.CAP_PROP_FPS)}") ```
## 写入视频 ### cv2.VideoWriter() ```python import cv2 # 打开输入视频 cap = cv2.VideoCapture('input.mp4') # 获取视频属性 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) # 定义编码器 fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 创建VideoWriter对象 out = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height)) while True: ret, frame = cap.read() if not ret: break # 处理帧(可选) # processed = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 写入帧 out.write(frame) cv2.imshow('Frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() out.release() cv2.destroyAllWindows() ``` ### 视频编码格式 ```python import cv2 # 常用编码器 fourcc_mp4v = cv2.VideoWriter_fourcc(*'mp4v') # MPEG-4 fourcc_xvid = cv2.VideoWriter_fourcc(*'XVID') # XVID fourcc_mjpg = cv2.VideoWriter_fourcc(*'MJPG') # Motion JPEG fourcc_h264 = cv2.VideoWriter_fourcc(*'H264') # H.264(需要支持) fourcc_hevc = cv2.VideoWriter_fourcc(*'HEVC') # H.265(需要支持) # 创建视频写入器 out = cv2.VideoWriter('output.mp4', fourcc_mp4v, 30, (1920, 1080)) ``` ### 录制摄像头视频 ```python import cv2 import datetime # 打开摄像头 cap = cv2.VideoCapture(0) # 获取视频属性 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = 30 # 定义编码器 fourcc = cv2.VideoWriter_fourcc(*'XVID') # 创建VideoWriter(文件名包含时间戳) filename = f"recording_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.avi" out = cv2.VideoWriter(filename, fourcc, fps, (width, height)) print("按'r'开始录制,按's'停止录制,按'q'退出") recording = False while True: ret, frame = cap.read() if not ret: break # 如果正在录制,写入帧 if recording: out.write(frame) # 显示录制状态 status = "Recording" if recording else "Not Recording" cv2.putText(frame, status, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow('Camera', frame) key = cv2.waitKey(1) & 0xFF if key == ord('r'): recording = True print("开始录制...") elif key == ord('s'): recording = False print("停止录制") elif key == ord('q'): break # 释放资源 cap.release() out.release() cv2.destroyAllWindows() ```
## 视频处理示例 ### 视频转灰度 ```python import cv2 cap = cv2.VideoCapture('input.mp4') # 获取视频属性 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) # 创建VideoWriter fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter('gray_output.mp4', fourcc, fps, (width, height), isColor=False) while True: ret, frame = cap.read() if not ret: break # 转灰度 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 写入 out.write(gray) cv2.imshow('Gray Video', gray) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() out.release() cv2.destroyAllWindows() ``` ### 视频帧提取 ```python import cv2 import os def extract_frames(video_path, output_dir, interval=1): """从视频中提取帧""" cap = cv2.VideoCapture(video_path) if not os.path.exists(output_dir): os.makedirs(output_dir) count = 0 frame_count = 0 while True: ret, frame = cap.read() if not ret: break # 按间隔提取 if frame_count % interval == 0: output_path = os.path.join(output_dir, f'frame_{count:06d}.jpg') cv2.imwrite(output_path, frame) count += 1 frame_count += 1 cap.release() print(f"提取了 {count} 帧到 {output_dir}") # 使用:每30帧提取1帧 extract_frames('video.mp4', 'frames', interval=30) ``` ### 视频速度调整 ```python import cv2 def change_video_speed(input_path, output_path, speed_factor=2.0): """调整视频播放速度""" cap = cv2.VideoCapture(input_path) fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps / speed_factor, (width, height)) while True: ret, frame = cap.read() if not ret: break out.write(frame) cap.release() out.release() # 使用:2倍速 change_video_speed('input.mp4', 'output_2x.mp4', speed_factor=2.0) ``` ### 视频拼接 ```python import cv2 def concatenate_videos(video_paths, output_path, direction='horizontal'): """拼接多个视频""" caps = [cv2.VideoCapture(path) for path in video_paths] # 获取第一个视频的属性 fps = caps[0].get(cv2.CAP_PROP_FPS) width = int(caps[0].get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(caps[0].get(cv2.CAP_PROP_FRAME_HEIGHT)) # 计算输出尺寸 if direction == 'horizontal': output_width = width * len(video_paths) output_height = height else: output_width = width output_height = height * len(video_paths) fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (output_width, output_height)) while True: frames = [] all_valid = True for cap in caps: ret, frame = cap.read() if ret: frames.append(frame) else: all_valid = False break if not all_valid: break # 拼接帧 if direction == 'horizontal': combined = cv2.hconcat(frames) else: combined = cv2.vconcat(frames) out.write(combined) for cap in caps: cap.release() out.release() # 使用 concatenate_videos(['video1.mp4', 'video2.mp4'], 'combined.mp4') ``` ### 视频处理总结 ``` 视频处理常用操作: 读取视频: ├─ 文件:VideoCapture('file.mp4') ├─ 摄像头:VideoCapture(0) └─ 网络流:VideoCapture('rtsp://url') 写入视频: ├─ 选择编码器 ├─ 设置帧率和分辨率 └─ 逐帧写入 处理操作: ├─ 逐帧处理图像 ├─ 添加文字/图形 ├─ 应用滤镜 └─ 合成多个视频 注意事项: ├─ 检查isOpened() ├─ 正确释放资源 ├─ 处理不同分辨率 └─ 考虑性能优化 ```
特征检测
## Harris角点检测 ### Harris角点检测原理 Harris角点检测是一种用于检测图像中角点的算法,角点是图像中灰度变化剧烈的点。 ```python import cv2 import numpy as np img = cv2.imread('image.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Harris角点检测 # blockSize: 邻域大小 # ksize: Sobel算子核大小 # k: Harris检测自由参数 gray = np.float32(gray) dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04) # 膨胀突出角点 dst = cv2.dilate(dst, None) # 阈值处理 img[dst > 0.01 * dst.max()] = [0, 0, 255] cv2.imshow('Harris Corners', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 优化Harris角点检测 ```python import cv2 import numpy as np def harris_corner_detection(image_path, threshold=0.01): """优化的Harris角点检测""" img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转浮点 gray = np.float32(gray) # Harris检测 dst = cv2.cornerHarris(gray, 2, 3, 0.04) # 膨胀 dst = cv2.dilate(dst, None) # 创建副本用于绘制 result = img.copy() # 找到角点坐标 corners = np.where(dst > threshold * dst.max()) # 绘制角点 for y, x in zip(corners[0], corners[1]): cv2.circle(result, (x, y), 3, (0, 0, 255), -1) return result result = harris_corner_detection('image.jpg') cv2.imshow('Harris Corners', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### Shi-Tomasi角点检测(goodFeaturesToTrack) ```python import cv2 import numpy as np img = cv2.imread('image.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Shi-Tomasi角点检测 # maxCorners: 最大角点数 # qualityLevel: 角点质量阈值 # minDistance: 角点间最小距离 corners = cv2.goodFeaturesToTrack( gray, maxCorners=100, qualityLevel=0.01, minDistance=10 ) # 绘制角点 result = img.copy() if corners is not None: for corner in corners: x, y = corner.ravel().astype(int) cv2.circle(result, (x, y), 3, (0, 0, 255), -1) cv2.imshow('Shi-Tomasi Corners', result) cv2.waitKey(0) cv2.destroyAllWindows() ```
## ORB特征检测 ### ORB简介 ORB(Oriented FAST and Rotated BRIEF)是一种快速的特征检测器和描述符,具有旋转不变性和尺度不变性。 ```python import cv2 img = cv2.imread('image.jpg') # 创建ORB对象 orb = cv2.ORB_create() # 检测关键点和计算描述符 keypoints, descriptors = orb.detectAndCompute(img, None) # 绘制关键点 result = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0), flags=0) cv2.imshow('ORB Keypoints', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### ORB参数调整 ```python import cv2 img = cv2.imread('image.jpg') # 创建ORB对象并设置参数 orb = cv2.ORB_create( nfeatures=1000, # 最大特征点数 scaleFactor=1.2, # 金字塔缩放因子 nlevels=8, # 金字塔层数 edgeThreshold=31, # 边缘阈值 firstLevel=0, # 第一层索引 WTA_K=2, # BRIEF点对数 scoreType=0, # 评分类型(0:Harris, 1:FAST) patchSize=31, # 补丁大小 fastThreshold=20 # FAST阈值 ) keypoints, descriptors = orb.detectAndCompute(img, None) result = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0)) cv2.imshow('ORB', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 其他特征检测器对比 ```python import cv2 img = cv2.imread('image.jpg') # ORB orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img, None) # BRISK brisk = cv2.BRISK_create() kp2, des2 = brisk.detectAndCompute(img, None) # AKAZE akaze = cv2.AKAZE_create() kp3, des3 = akaze.detectAndCompute(img, None) # 绘制比较 img1 = cv2.drawKeypoints(img, kp1, None, color=(0, 255, 0)) img2 = cv2.drawKeypoints(img, kp2, None, color=(0, 255, 0)) img3 = cv2.drawKeypoints(img, kp3, None, color=(0, 255, 0)) print(f"ORB: {len(kp1)} keypoints") print(f"BRISK: {len(kp2)} keypoints") print(f"AKAZE: {len(kp3)} keypoints") cv2.imshow('ORB', img1) cv2.imshow('BRISK', img2) cv2.imshow('AKAZE', img3) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 特征检测器选择 | 检测器 | 速度 | 旋转不变性 | 尺度不变性 | 适用场景 | |-------|------|-----------|-----------|----------| | ORB | 很快 | 是 | 是 | 实时应用 | | BRISK | 快 | 是 | 是 | 移动应用 | | AKAZE | 中等 | 是 | 是 | 高精度要求 | | SIFT | 慢 | 是 | 是 | 精确匹配 | | SURF | 慢 | 是 | 是 | 大图像 |
## 特征匹配 ### 暴力匹配器 ```python import cv2 img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE) img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE) # 创建ORB对象 orb = cv2.ORB_create() # 检测特征点和计算描述符 kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) # 创建BFMatcher bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) # 匹配描述符 matches = bf.match(des1, des2) # 按距离排序 matches = sorted(matches, key=lambda x: x.distance) # 绘制前50个匹配 result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=2) cv2.imshow('Matches', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### FLANN匹配器 ```python import cv2 import numpy as np img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE) img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE) # 创建SIFT对象(需要opencv-contrib) sift = cv2.SIFT_create() kp1, des1 = sift.detectAndCompute(img1, None) kp2, des2 = sift.detectAndCompute(img2, None) # FLANN参数 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1, des2, k=2) # 应用比率测试 good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) # 绘制匹配 result = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=2) cv2.imshow('FLANN Matches', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 图像拼接示例 ```python import cv2 import numpy as np def stitch_images(image1_path, image2_path): """简单图像拼接""" img1 = cv2.imread(image1_path) img2 = cv2.imread(image2_path) # 转灰度 gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 创建ORB对象 orb = cv2.ORB_create() # 检测特征 kp1, des1 = orb.detectAndCompute(gray1, None) kp2, des2 = orb.detectAndCompute(gray2, None) # 匹配特征 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) # 排序并选择好的匹配 matches = sorted(matches, key=lambda x: x.distance)[:50] # 获取匹配点坐标 src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) # 计算单应性矩阵 M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) # 应用透视变换 h, w = img1.shape[:2] result = cv2.warpPerspective(img1, M, (w + img2.shape[1], h)) result[0:h, 0:w] = img2 return result stitched = stitch_images('image1.jpg', 'image2.jpg') cv2.imshow('Stitched', stitched) cv2.waitKey(0) cv2.destroyAllWindows() ``` ### 特征匹配应用场景 ``` 特征匹配典型应用: 图像拼接: └─ 全景图生成、文档扫描 物体识别: └─ 识别图像中的物体 图像检索: └─ 在数据库中查找相似图像 人脸识别: └─ 人脸特征提取和匹配 视觉定位: └─ 机器人导航、AR应用 3D重建: └─ 从多角度图像重建3D模型 ```
参考