昨天我们学习了计算机视觉的理论基础——知道了图像在计算机眼中是数字矩阵,理解了卷积如何提取特征,也看到了CNN如何一步步学会“看”。
但问题来了:在实际项目中,你怎么把一张照片读进Python?怎么把彩色图转成灰度?怎么检测人脸?
今天,我们学习OpenCV——计算机视觉领域最基础、最强大、最普及的工具库。
OpenCV是什么?
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,包含2500+优化算法,涵盖图像处理、视频分析、目标检测、机器学习等几乎所有视觉任务。
它就像视觉工程师的“瑞士军刀”——你遇到的大多数图像处理问题,OpenCV都有现成函数。
OpenCV的安装与核心理念
安装OpenCV
# 标准安装(包含主要模块)pip install opencv-python# 如果需要额外模块(如跟踪算法)pip install opencv-contrib-python# 验证安装import cv2print(f"OpenCV版本: {cv2.__version__}")# 应输出 4.5.x 或更高版本
OpenCV的核心理念:一切皆矩阵
OpenCV读取的图像,本质上就是NumPy数组——这意味着你可以用NumPy的所有功能操作图像!
import cv2import numpy as np# 读取图像img = cv2.imread('photo.jpg')print(type(img)) # <class 'numpy.ndarray'>print(img.shape) # (高度, 宽度, 通道数) 例如 (480, 640, 3)# 可以用NumPy方式操作roi = img[100:200, 200:300] # 裁剪感兴趣区域img[:, :, 0] = 0 # 将蓝色通道全部置0
关键认知:OpenCV + NumPy = 图像处理的“倚天剑+屠龙刀”。图像的读写与显示 —— Hello World
读取图像:cv2.imread()
import cv2# 最基本的读取——彩色模式(默认)img_color = cv2.imread('cat.jpg')# 等价于 img_color = cv2.imread('cat.jpg', cv2.IMREAD_COLOR)# 灰度模式读取img_gray = cv2.imread('cat.jpg', cv2.IMREAD_GRAYSCALE)# 保留Alpha通道(透明度)img_unchanged = cv2.imread('cat.png', cv2.IMREAD_UNCHANGED)# 检查是否读取成功(文件路径错误时不会报错,但返回None)if img_color is None: print("错误:无法读取图像,请检查文件路径") exit()
重要提示:OpenCV读取彩色图像时,默认通道顺序是 BGR(蓝-绿-红),而不是常见的RGB!这一点常被初学者忽视,导致用Matplotlib显示时颜色异常。
显示图像:cv2.imshow()
# 创建窗口并显示图像cv2.imshow('My Cat', img_color)# 等待按键(必须!否则窗口一闪而过)cv2.waitKey(0) # 0表示无限等待,直到按下任意键# 关闭所有窗口cv2.destroyAllWindows()
cv2.waitKey()的秘密:
保存图像:cv2.imwrite()
# 保存图像(格式由文件扩展名决定)cv2.imwrite('cat_gray.jpg', img_gray) # 保存为JPEGcv2.imwrite('cat_copy.png', img_color) # 保存为PNG
完整示例:读取→显示→保存
import cv2# 读取图像img = cv2.imread('cat.jpg', cv2.IMREAD_COLOR)# 检查if img is None: print("文件读取失败") exit()# 显示原图cv2.imshow('Original Cat', img)# 转换为灰度并显示gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.imshow('Gray Cat', gray)# 保存灰度图cv2.imwrite('cat_gray.jpg', gray)# 等待按键cv2.waitKey(0)cv2.destroyAllWindows()
颜色空间转换 —— 让颜色更“听话”
为什么要转换颜色空间?
RGB虽然符合人类视觉,但在计算机视觉任务中并不是最佳选择:
BGR ↔ 灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
BGR ↔ HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
HSV各通道范围(OpenCV中):
H(色调):0~179
S(饱和度):0~255
V(明度):0~255
实战:提取蓝色物体
import cv2import numpy as np# 读取图像img = cv2.imread('colorful_balls.jpg')# 转换为HSVhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)# 定义蓝色的HSV范围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()
这段代码能做什么?—— 在彩色球堆中,只把蓝色的球“抠”出来,其他变黑。这是颜色追踪的基础。图像的几何变换 —— 移动、旋转、缩放
缩放:cv2.resize()
# 按指定尺寸缩放resized = cv2.resize(img, (300, 200))# 按比例缩放scale_percent = 50 # 缩小到50%width = int(img.shape[1] * scale_percent / 100)height = int(img.shape[0] * scale_percent / 100)resized = cv2.resize(img, (width, height))# 插值方法的选择# 缩小:INTER_AREA 效果最好# 放大:INTER_LINEAR 或 INTER_CUBICresized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
平移:cv2.warpAffine()
# 定义平移矩阵 [1, 0, tx; 0, 1, ty]tx, ty = 50, 100 # 向右移50,向下移100M = np.float32([[1, 0, tx], [0, 1, ty]])# 应用仿射变换shifted = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
旋转:cv2.getRotationMatrix2D()
# 获取旋转矩阵h, w = img.shape[:2]center = (w // 2, h // 2) # 旋转中心angle = 45 # 逆时针45度scale = 1.0 # 不缩放M = cv2.getRotationMatrix2D(center, angle, scale)# 应用旋转rotated = cv2.warpAffine(img, M, (w, h))
仿射变换:三点对应
仿射变换是“线性变换+平移”,可以完成旋转、缩放、剪切等操作。OpenCV提供了根据三个对应点计算变换矩阵的函数:
# 原图中的三个点pts1 = np.float32([[50, 50], [200, 50], [50, 200]])# 目标图中的三个点pts2 = np.float32([[10, 100], [200, 50], [100, 250]])# 计算仿射变换矩阵M = cv2.getAffineTransform(pts1, pts2)# 应用变换dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
透视变换:四点对应
当需要处理倾斜视角(如拍摄的文档、路牌)时,透视变换是利器:
# 原图中的四个角点(通常手动选取)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)# 应用变换dst = cv2.warpPerspective(img, M, (300, 300))
图像滤波 —— 去噪与增强
低通滤波:平滑/模糊
# 均值滤波(每个像素替换为邻域平均值)blur = cv2.blur(img, (5, 5))# 高斯滤波(邻域加权平均,中心权重高)gaussian = cv2.GaussianBlur(img, (5, 5), 1.5)# 中值滤波(对椒盐噪声特别有效)median = cv2.medianBlur(img, 5)
参数解释:
(5, 5):滤波器核大小(必须是奇数)
1.5:高斯核的标准差(σ)
高通滤波:边缘检测
作用:提取图像中灰度变化剧烈的地方——即边缘。
Sobel算子:一阶导数
# 计算x方向的梯度sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)# 计算y方向的梯度sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=5)# 计算梯度幅值sobel = cv2.magnitude(sobelx, sobely)sobel = cv2.convertScaleAbs(sobel) # 转为8位图像
Canny边缘检测:最常用的边缘算法
Canny算法包括高斯滤波 + 梯度计算 + 非极大值抑制 + 双阈值处理,效果通常最好。
# 简单的两行代码,效果惊人edges = cv2.Canny(gray, threshold1=100, threshold2=200)# 显示结果cv2.imshow('Edges', edges)
参数含义:
视频处理 —— 让静态图像动起来
从摄像头捕获实时视频
import cv2# 打开摄像头(0表示第一个摄像头)cap = cv2.VideoCapture(0)# 检查是否成功打开if not cap.isOpened(): print("无法打开摄像头") exit()while True: # 逐帧捕获 ret, frame = cap.read() # 如果正确读取帧,ret为True if not ret: print("无法接收帧,退出...") break # 处理帧(例如转为灰度) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 显示结果 cv2.imshow('Camera', gray) # 按'q'键退出 if cv2.waitKey(1) == ord('q'): break# 释放资源cap.release()cv2.destroyAllWindows()
从视频文件读取
cap = cv2.VideoCapture('video.mp4') # 只需替换为文件路径# 其余代码与摄像头相同
保存视频
# 定义视频编码器和输出对象fourcc = cv2.VideoWriter_fourcc(*'XVID') # 或 'MJPG', 'X264'out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))while True: ret, frame = cap.read() if not ret: break # 处理帧(例如水平翻转) frame = cv2.flip(frame, 1) # 写入帧 out.write(frame) cv2.imshow('Frame', frame) if cv2.waitKey(1) == ord('q'): break# 释放所有资源cap.release()out.release()cv2.destroyAllWindows()
实战项目——摄像头人脸检测与跟踪
让我们把今天学的所有知识整合起来,做一个完整的计算机视觉应用——实时人脸检测与跟踪。
核心思路
人脸检测:使用Haar级联分类器在每帧中查找人脸
人脸跟踪:检测到人脸后,用CSRT跟踪器持续追踪
状态切换:跟踪失败时,自动切换回检测模式
完整代码实现
import cv2class FaceTracker: """实时人脸检测+跟踪器""" def __init__(self): # 加载预训练的人脸检测模型 self.face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) self.tracker = None self.tracking = False def detect_face(self, frame): """检测人脸""" gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale( gray, scaleFactor=1.1, # 图像金字塔缩放比例 minNeighbors=5, # 每个候选框需要保留的邻域数 minSize=(30, 30) # 最小人脸尺寸 ) return faces def init_tracker(self, frame, bbox): """初始化跟踪器""" self.tracker = cv2.TrackerCSRT_create() self.tracker.init(frame, tuple(bbox)) self.tracking = True def update_tracker(self, frame): """更新跟踪""" success, bbox = self.tracker.update(frame) return success, bbox def process_frame(self, frame): """处理单帧图像""" # 如果当前未跟踪,执行检测 if not self.tracking: faces = self.detect_face(frame) if len(faces) > 0: # 跟踪第一个检测到的人脸 bbox = faces[0] # (x, y, w, h) self.init_tracker(frame, bbox) # 绘制检测框 x, y, w, h = bbox cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText(frame, 'Detection', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) else: # 跟踪模式 success, bbox = self.update_tracker(frame) if success: x, y, w, h = [int(v) for v in bbox] cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2) cv2.putText(frame, 'Tracking', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) else: # 跟踪失败,下一帧重新检测 self.tracking = False return frame# 主程序def main(): cap = cv2.VideoCapture(0) tracker = FaceTracker() while True: ret, frame = cap.read() if not ret: break # 处理帧 processed_frame = tracker.process_frame(frame) # 显示 cv2.imshow('Face Tracking', processed_frame) # 按'q'退出 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()if __name__ == '__main__': main()
代码解析
| | |
|---|
| Haar检测 | | detectMultiScale |
| CSRT跟踪 | | |
| 状态切换 | | |
运行效果:打开摄像头后,首先检测到人脸用绿色框标注,然后转为蓝色框跟踪;如果人脸移出画面或遮挡,系统自动重新检测。
从OpenCV开始,走进视觉世界
今天,我们学习了OpenCV最核心的基础知识:
| | |
|---|
| imread | |
| cvtColor | |
| warpAffine | |
| blur | |
| VideoCapture | |
| CascadeClassifier | |
OpenCV最大的价值:它不是黑箱,而是工具箱——你清楚地知道每个函数在做什么,并且可以组合它们解决实际问题。