参考:《计算机视觉开发实践》朱文伟 李建英 著
3.1 OpenCV 架构概览
opencv-python 的架构与 C/C++ 版本一致,Python 层仅提供函数封装。常用模块:
- core:核心功能,包含基本数据结构、绘图函数、数组操作和动态数据结构。
- dnn:深度学习模块(OpenCV 4 特性),支持加载序列化模型与前向推理(不支持训练)。
- features2d:特征点相关(检测、描述、匹配)。
- flann:高维近似最近邻搜索与聚类。
- gapi:图像处理加速框架。
- highgui:图形界面(窗口、鼠标、键盘、可视化交互)。
- imgcodecs:图像文件读写。
- imgproc:图像处理(滤波、几何变换、直方图、特征/目标检测等)。
- ml:机器学习(分类、回归、聚类)。
- objdetect:目标检测(如 Haar 特征)。
- photo:计算摄影(修复、去噪)。
- stitching:图像拼接(匹配、旋转估计、自动校准、接缝估计等)。
- video:视频分析(运动估计、背景分离、跟踪)。
- videoio:视频/图像序列 读写。
3.2 图像输入输出模块 imgcodecs
3.2.1 读取图像(cv2.imread)
函数用于从文件读取图像:
cv2.imread(filename[, flags]) -> retval
- flags 取值:
- cv.IMREAD_ANYDEPTH(2):若原图为 16/32 位深度,则按对应深度返回,否则转 8 位。
- cv.IMREAD_COLOR(1):读取为彩色 BGR(3 通道)。
- cv.IMREAD_GRAYSCALE(0):读取为灰度(1 通道)。
- cv.IMREAD_UNCHANGED(-1):不做改变,按原图返回(含 alpha 时保留)。
- 返回:读取成功返回包含像素数据的 NumPy 数组;失败返回 None(如文件不存在/权限问题/格式不支持)。
示例:
import sys
import cv2 as cv
img = cv.imread("p1.jpg")
if img is None:
sys.exit("Could not read the p1.jpg.")
- 提示:imread 根据文件内容判断格式,而非扩展名。
- 中文路径示例(使用 imdecode):
import cv2 as cv
import numpy as np
imgpath = r"中文路径/图片.png"
img = cv.imdecode(np.fromfile(imgpath, dtype=np.uint8), -1)
3.2.2 图像尺寸与通道(shape)
读取成功后得到的是 NumPy 数组,可使用 shape 获取尺寸:
- shape[0]:行数(高度 height)
- shape[1]:列数(宽度 width)
- shape[2]:通道数(如 3 表示 BGR)
3.2.3 保存图像(cv2.imwrite)
保存图像到文件:
cv2.imwrite(filename, img[, params]) -> retval
- filename:目标文件名(含后缀,如 123.png)。
- img:要保存的图像(NumPy 数组)。
- params:特定格式保存参数(可选)。
常见支持:
- 8 位单通道或 BGR 三通道图像。
- 16 位无符号(CV_16U):可保存为 PNG/JPEG/TIFF。
- 32 位浮点(CV_32F):可保存为 PFM/TIFF/OpenEXR/Radiance HDR。
- 3 通道(CV_32FC3)TIFF 使用 LogLuv 高动态范围编码。
- 支持带 alpha 的 PNG:使用 8/16 位的 BGRA(alpha 在最后,0 透明,255/65535 不透明)。
- 如格式/深度/通道顺序不同,保存前可用 convertTo/cvtColor 转换;或使用通用存储(XML/YAML)。
3.3 OpenCV 界面编程(HighGUI)
HighGUI 提供窗口、控件与事件处理(鼠标、键盘、滑动条等),便于图像可视化与交互。
3.3.1 新建窗口(cv2.namedWindow)
cv2.namedWindow(winname[, flags]) -> None
- flags:
- cv.WINDOW_AUTOSIZE:窗口大小自适应图片,且不可手动更改。
- cv.WINDOW_NORMAL:允许用户改变窗口大小。
- cv.WINDOW_OPENGL:创建支持 OpenGL 的窗口。
3.3.2 显示图像(cv2.imshow)与显示规则
cv2.imshow(winname, mat) -> None
不同深度的显示规则:
- 8U:直接显示。
- 16U/32S:内部将像素值除以 256 映射到 [0, 255]。
- 32F/64F:内部将像素值乘以 255 映射到 [0, 255](需先归一化到 [0, 1])。
3.3.3 驻留屏幕与键盘监听(cv2.waitKey)
cv2.waitKey([delay]) -> retval
- delay <= 0:无限等待键盘事件;> 0:等待 delay 毫秒。
- imshow 之后应调用 waitKey,否则窗口可能瞬间关闭。
- 返回值为按键 ASCII 码,超时未按键返回 -1。
3.3.4 单窗口显示多幅图像(numpy.hstack/vstack)
- numpy.hstack(tup):将行数相同的数组/矩阵按列方向水平拼接。
- 在多路视频/图像预览时,可先统一尺寸后水平/垂直拼接到一个窗口中显示。
3.3.5 销毁窗口
cv2.destroyWindow(winname) -> None:销毁指定窗口。cv2.destroyAllWindows() -> None:销毁所有窗口。
3.3.6 调整窗口大小(cv2.resizeWindow)
cv2.resizeWindow(winname, width, height) -> None
- 需先以
cv.WINDOW_NORMAL创建窗口,方可调整大小。
3.3.7 鼠标事件回调(cv2.setMouseCallback)
cv2.setMouseCallback(windowName, onMouse, param=None) -> None
回调签名:
def MouseCallback(event, x, y, flags, param):
...
- event:鼠标事件类型;x/y:坐标;flags:事件标志;param:用户参数。
3.3.8 键盘回调(基于 waitKey)
- 通过
cv2.waitKey的返回值判断按键并编写响应逻辑。 - 常与
cv2.imshow搭配用于交互式处理流程。
3.3.9 滑动条(cv2.createTrackbar / getTrackbarPos / setTrackbarPos)
创建滑动条:
cv2.createTrackbar(trackbarName, windowName, value, count, onChange) -> None
- trackbarName:滑动条名称;windowName:父窗口名称。
- value:初始值(整数,可变);count:最大值;onChange:回调函数。
回调签名:
def TrackbarCallback(pos):
...
- pos:滚动块当前位置。
- 若不需要回调,可传 None,此时仅通过变量值变化响应。
读取/设置滑动条:
cv2.getTrackbarPos(trackbarName, windowName) -> intcv2.setTrackbarPos(trackbarName, windowName, pos) -> None
Demo(直接copy,记得更改图片地址)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OpenCV 基本操作演示脚本
- 读取/保存图像:cv2.imread / cv2.imdecode(支持中文路径)/ cv2.imwrite
- 图像尺寸与通道:shape
- HighGUI:namedWindow / imshow / waitKey / destroyAllWindows / resizeWindow
- 单窗口多图:numpy.hstack / numpy.vstack
- 鼠标事件:cv2.setMouseCallback(左键添加点,右键撤销)
- 键盘事件:基于 waitKey(s 保存, r 重置, q/ESC 退出)
- 滑动条:cv2.createTrackbar / getTrackbarPos / setTrackbarPos(亮度、对比、模糊、灰度开关)
运行:python opencv_demo.py --image "你的图片路径(可含中文)"
"""
import os
import sys
import cv2 as cv
import numpy as np
import argparse
from datetime import datetime
def setup_console_utf8():
"""尽量让控制台按 UTF-8 编码,避免中文输出乱码(尤其是 Windows)。"""
# Python 3.7+ 支持 reconfigure
for stream_name in ("stdin", "stdout", "stderr"):
try:
stream = getattr(sys, stream_name)
if hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
# Windows 下设置控制台代码页为 UTF-8
if os.name == 'nt':
try:
import ctypes # noqa: F401
ctypes.windll.kernel32.SetConsoleCP(65001)
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
except Exception:
pass
setup_console_utf8()
def imread_smart(path: str, flags: int = cv.IMREAD_UNCHANGED):
"""读取图像,优先使用 imdecode 以支持中文路径。"""
if path and os.path.exists(path):
try:
data = np.fromfile(path, dtype=np.uint8)
img = cv.imdecode(data, flags)
return img
except Exception:
return cv.imread(path, flags)
return None
def make_synthetic(w: int = 640, h: int = 360) -> np.ndarray:
"""生成一张合成测试图像。"""
x = np.linspace(0, 255, w, dtype=np.uint8)
y = np.linspace(0, 255, h, dtype=np.uint8)
xv, yv = np.meshgrid(x, y)
img = np.dstack((xv, yv, ((xv + yv) // 2).astype(np.uint8)))
cv.circle(img, (w // 2, h // 2), min(w, h) // 4, (0, 255, 255), 4)
cv.putText(img, 'Synthetic', (20, 40), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
return img
def to_bgr(img: np.ndarray) -> np.ndarray:
"""统一转为 BGR 三通道,便于后续处理与拼接。"""
if img is None:
return None
if len(img.shape) == 2:
return cv.cvtColor(img, cv.COLOR_GRAY2BGR)
if img.shape[2] == 4:
return cv.cvtColor(img, cv.COLOR_BGRA2BGR)
return img
points: list[tuple[int, int]] = []
def on_mouse(event, x, y, flags, param):
"""鼠标事件:左键添加点,右键撤销最近点。"""
global points
if event == cv.EVENT_LBUTTONDOWN:
points.append((x, y))
elif event == cv.EVENT_RBUTTONDOWN:
if points:
points.pop()
def ensure_odd(k: int) -> int:
return k if k % 2 == 1 else k + 1
def resize_to(img: np.ndarray, size: tuple[int, int]) -> np.ndarray:
return cv.resize(img, size, interpolation=cv.INTER_AREA)
def annotate(img: np.ndarray, text: str, org=(10, 25)) -> np.ndarray:
out = img.copy()
cv.putText(out, text, org, cv.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2, cv.LINE_AA)
return out
def main():
parser = argparse.ArgumentParser(description='OpenCV 基本操作演示')
parser.add_argument('--image', '-i', type=str, default='D:\\Unity\\Python\\OpenCV\\03-opencv基本操作\\test.jpg', help='文件路径')
parser.add_argument('--width', type=int, default=400, help='1024')
parser.add_argument('--height', type=int, default=300, help='1024')
args = parser.parse_args()
img = imread_smart(args.image) if args.image else None
if img is None:
img = make_synthetic()
print('未提供或无法读取图片,使用合成图像。')
img = to_bgr(img)
h, w = img.shape[0], img.shape[1]
c = img.shape[2] if len(img.shape) == 3 else 1
print(f'原图尺寸: width={w}, height={h}, channels={c}')
win = 'OpenCV Demo'
cv.namedWindow(win, cv.WINDOW_NORMAL)
cv.resizeWindow(win, 2 * args.width + 60, 2 * args.height + 120)
# 滑动条:亮度、对比度、模糊、灰度开关
cv.createTrackbar('Brightness', win, 100, 200, lambda v: None) # 0..200, beta=v-100
cv.createTrackbar('Contrast', win, 100, 200, lambda v: None) # 0..200, alpha=v/100
cv.createTrackbar('Blur', win, 0, 20, lambda v: None) # 0..20, kernel=2*v+1
cv.createTrackbar('Gray', win, 0, 1, lambda v: None) # 0/1
# 鼠标事件
cv.setMouseCallback(win, on_mouse, None)
while True:
b = cv.getTrackbarPos('Brightness', win)
cval = cv.getTrackbarPos('Contrast', win)
blur = cv.getTrackbarPos('Blur', win)
grayflag = cv.getTrackbarPos('Gray', win)
alpha = max(cval, 1) / 100.0
beta = b - 100
processed = cv.convertScaleAbs(img, alpha=alpha, beta=beta)
if blur > 0:
k = ensure_odd(2 * blur + 1)
processed = cv.GaussianBlur(processed, (k, k), 0)
gray = cv.cvtColor(processed, cv.COLOR_BGR2GRAY) if grayflag else cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray_bgr = cv.cvtColor(gray, cv.COLOR_GRAY2BGR)
# 绘制鼠标点
overlay = processed.copy()
for i, (px, py) in enumerate(points):
cv.circle(overlay, (px, py), 5, (0, 0, 255), -1, cv.LINE_AA)
cv.putText(overlay, f'{i + 1}:({px},{py})', (px + 8, py - 8), cv.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 1,
cv.LINE_AA)
# 准备拼接视图
tile_w, tile_h = args.width, args.height
tile1 = resize_to(img, (tile_w, tile_h))
tile1 = annotate(tile1, f'Original {w}x{h}x{c}')
tile2 = resize_to(overlay, (tile_w, tile_h))
tile2 = annotate(tile2, f'Processed a={alpha:.2f} b={beta} blur={blur}')
tile3 = resize_to(gray_bgr, (tile_w, tile_h))
tile3 = annotate(tile3, 'Gray' if grayflag else 'Gray (from original)')
# 辅助图:Canny 边缘
edges = cv.Canny(gray, 50, 150)
edges_bgr = cv.cvtColor(edges, cv.COLOR_GRAY2BGR)
tile4 = resize_to(edges_bgr, (tile_w, tile_h))
tile4 = annotate(tile4, 'Canny')
top = np.hstack([tile1, tile2])
bottom = np.hstack([tile3, tile4])
canvas = np.vstack([top, bottom])
cv.imshow(win, canvas)
key = cv.waitKey(1) & 0xFF
if key == ord('s'):
out_name = f'output_{datetime.now().strftime("%Y%m%d_%H%M%S")}.png'
cv.imwrite(out_name, canvas)
print(f'已保存: {out_name}')
elif key == ord('r'):
# 重置滑动条与标注点
cv.setTrackbarPos('Brightness', win, 100)
cv.setTrackbarPos('Contrast', win, 100)
cv.setTrackbarPos('Blur', win, 0)
cv.setTrackbarPos('Gray', win, 0)
points.clear()
print('已重置参数与标注点。')
elif key in (27, ord('q')):
break
cv.destroyAllWindows()
if __name__ == '__main__':
main()
