1. OpenCV 是什么
OpenCV 是一个开源计算机视觉库,覆盖图像处理、视频处理、特征检测、相机标定、三维重建、目标检测、传统机器学习、DNN 推理等方向。
在 Python 中一般通过 cv2 模块使用:
import cv2 as cv
OpenCV 的优势:
- API 覆盖面广,适合快速做视觉原型。
- C++ 实现,Python 调用也能有不错性能。
- 和 NumPy 深度配合,图像就是数组。
- 传统视觉算法非常丰富,适合工程落地。
- 能处理图片、视频流、摄像头、标定、几何变换等完整链路。
2. 安装
官方文档目前推荐大多数 Python 用户优先使用 PyPI 包。建议在项目目录中创建虚拟环境,避免依赖混乱。
2.1 创建虚拟环境
Windows PowerShell:
python –m venv .venv
.\\.venv\\Scripts\\activate
python –m pip install —upgrade pip setuptools wheel
Linux/macOS:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install –upgrade pip setuptools wheel
2.2 选择安装包
四个常见包只能选一个装在同一个环境里:
pip install opencv-python
适合大多数本地开发,有 GUI、图片和视频功能。
pip install opencv-contrib-python
包含额外 contrib 模块,例如一些扩展特征、跟踪、结构光等。
pip install opencv-python-headless
适合服务器、Docker、CI,不需要 imshow 窗口。
pip install opencv-contrib-python-headless
contrib + 无 GUI 版本。
2.3 验证安装
import cv2 as cv
print(cv.__version__)
如果能输出版本号,说明安装成功。
3. OpenCV 的核心心智模型
3.1 图像就是 NumPy 数组
OpenCV 读入图片后得到一个 numpy.ndarray:
img = cv.imread("demo.jpg")
print(type(img))
print(img.shape)
print(img.dtype)
常见输出:
<class 'numpy.ndarray'>
(1080, 1920, 3)
uint8
含义:
- shape[0] 是高度 height
- shape[1] 是宽度 width
- shape[2] 是通道数 channel
- uint8 表示每个通道范围通常是 0 到 255
3.2 坐标顺序要分清
NumPy 访问像素:
pixel = img[y, x]
OpenCV 画图和几何 API 里点坐标通常是:
(x, y)
这点非常容易写反。
3.3 OpenCV 默认是 BGR,不是 RGB
cv.imread 读入彩色图像时通道顺序是 BGR:
img_bgr = cv.imread("demo.jpg")
img_rgb = cv.cvtColor(img_bgr, cv.COLOR_BGR2RGB)
如果用 Matplotlib 显示 OpenCV 图像,一般要先转 RGB:
import matplotlib.pyplot as plt
plt.imshow(cv.cvtColor(img_bgr, cv.COLOR_BGR2RGB))
plt.axis("off")
plt.show()
4. 图片读取、显示和保存
4.1 读取图片
import cv2 as cv
img = cv.imread("input.jpg")
if img is None:
raise FileNotFoundError("图片读取失败,请检查路径")
读取灰度图:
gray = cv.imread("input.jpg", cv.IMREAD_GRAYSCALE)
保留 alpha 通道:
img = cv.imread("input.png", cv.IMREAD_UNCHANGED)
4.2 显示图片
本地有 GUI 的环境:
cv.imshow("image", img)
cv.waitKey(0)
cv.destroyAllWindows()
服务器或 Notebook 中建议用 Matplotlib 或保存文件。
4.3 保存图片
cv.imwrite("output.jpg", img)
保存 PNG:
cv.imwrite("output.png", img)
控制 JPEG 质量:
cv.imwrite("output.jpg", img, [cv.IMWRITE_JPEG_QUALITY, 95])
5. 基础图像操作
5.1 裁剪 ROI
roi = img[y1:y2, x1:x2]
cv.imwrite("roi.jpg", roi)
5.2 修改像素
img[100, 200] = (0, 0, 255) # BGR,红色
5.3 拆分和合并通道
b, g, r = cv.split(img)
merged = cv.merge([b, g, r])
多数情况下直接用 NumPy 切片更快:
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]
5.4 改变亮度和对比度
alpha = 1.2 # 对比度
beta = 20 # 亮度
out = cv.convertScaleAbs(img, alpha=alpha, beta=beta)
6. 颜色空间
6.1 BGR 转灰度
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
6.2 BGR 转 HSV
HSV 常用于颜色分割:
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
提取红色区域示例:
import cv2 as cv
import numpy as np
img = cv.imread("input.jpg")
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
lower1 = np.array([0, 80, 80])
upper1 = np.array([10, 255, 255])
lower2 = np.array([170, 80, 80])
upper2 = np.array([180, 255, 255])
mask1 = cv.inRange(hsv, lower1, upper1)
mask2 = cv.inRange(hsv, lower2, upper2)
mask = cv.bitwise_or(mask1, mask2)
result = cv.bitwise_and(img, img, mask=mask)
cv.imwrite("red_mask.png", mask)
cv.imwrite("red_result.jpg", result)
7. 绘图和标注
OpenCV 常用于在图像上画检测结果。
img = cv.imread("input.jpg")
cv.line(img, (20, 20), (300, 20), (255, 0, 0), 2)
cv.rectangle(img, (50, 50), (250, 180), (0, 255, 0), 2)
cv.circle(img, (150, 120), 40, (0, 0, 255), –1)
cv.putText(
img,
"OpenCV",
(50, 240),
cv.FONT_HERSHEY_SIMPLEX,
1.0,
(255, 255, 255),
2,
cv.LINE_AA,
)
cv.imwrite("annotated.jpg", img)
颜色仍然是 BGR。
8. 几何变换
8.1 Resize
small = cv.resize(img, None, fx=0.5, fy=0.5, interpolation=cv.INTER_AREA)
指定尺寸:
resized = cv.resize(img, (640, 360))
常用插值:
- cv.INTER_AREA:缩小常用。
- cv.INTER_LINEAR:默认通用。
- cv.INTER_CUBIC:放大质量较好但更慢。
8.2 翻转
flip_h = cv.flip(img, 1) # 水平翻转
flip_v = cv.flip(img, 0) # 垂直翻转
flip_b = cv.flip(img, –1) # 水平+垂直
8.3 旋转
h, w = img.shape[:2]
center = (w // 2, h // 2)
M = cv.getRotationMatrix2D(center, 30, 1.0)
rotated = cv.warpAffine(img, M, (w, h))
8.4 透视变换
import numpy as np
src = np.float32([[100, 100], [500, 120], [80, 400], [520, 420]])
dst = np.float32([[0, 0], [400, 0], [0, 300], [400, 300]])
M = cv.getPerspectiveTransform(src, dst)
warped = cv.warpPerspective(img, M, (400, 300))
适合做文档矫正、平面投影校正等。
9. 滤波和降噪
9.1 均值滤波
blur = cv.blur(img, (5, 5))
9.2 高斯滤波
gaussian = cv.GaussianBlur(img, (5, 5), 0)
适合去除自然噪声,也常作为边缘检测前处理。
9.3 中值滤波
median = cv.medianBlur(img, 5)
适合椒盐噪声。
9.4 双边滤波
bilateral = cv.bilateralFilter(img, 9, 75, 75)
能尽量保边去噪,但比普通滤波慢。
10. 阈值和二值化
10.1 固定阈值
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
10.2 Otsu 自动阈值
_, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
10.3 自适应阈值
适合光照不均匀的图像:
binary = cv.adaptiveThreshold(
gray,
255,
cv.ADAPTIVE_THRESH_GAUSSIAN_C,
cv.THRESH_BINARY,
11,
2,
)
11. 形态学操作
形态学通常作用于二值图或 mask。
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
腐蚀:
eroded = cv.erode(binary, kernel, iterations=1)
膨胀:
dilated = cv.dilate(binary, kernel, iterations=1)
开运算:先腐蚀后膨胀,去小噪点。
opened = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
闭运算:先膨胀后腐蚀,填小孔。
closed = cv.morphologyEx(binary, cv.MORPH_CLOSE, kernel)
12. 边缘检测
Canny 是最常用的边缘检测算法之一。
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = cv.GaussianBlur(gray, (5, 5), 0)
edges = cv.Canny(gray, 50, 150)
cv.imwrite("edges.png", edges)
经验:
- 先轻微高斯模糊,减少噪声。
- 低阈值和高阈值需要按图像调。
- 边缘结果常与轮廓、霍夫变换一起用。
13. 轮廓检测
轮廓适合分析二值区域的形状、面积、位置。
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
contours, hierarchy = cv.findContours(
binary,
cv.RETR_EXTERNAL,
cv.CHAIN_APPROX_SIMPLE,
)
canvas = img.copy()
for cnt in contours:
area = cv.contourArea(cnt)
if area < 100:
continue
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(canvas, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv.putText(canvas, f"{area:.0f}", (x, y – 5),
cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
cv.imwrite("contours.jpg", canvas)
常用函数:
- cv.contourArea(cnt):面积。
- cv.arcLength(cnt, True):周长。
- cv.boundingRect(cnt):外接矩形。
- cv.minAreaRect(cnt):最小旋转外接矩形。
- cv.approxPolyDP(…):多边形近似。
14. 特征检测和匹配
特征点用于图像拼接、定位、匹配、跟踪等。
14.1 ORB 特征
ORB 免费、快速、常用于工程。
img1 = cv.imread("a.jpg", cv.IMREAD_GRAYSCALE)
img2 = cv.imread("b.jpg", cv.IMREAD_GRAYSCALE)
orb = cv.ORB_create(nfeatures=1000)
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda m: m.distance)
vis = cv.drawMatches(img1, kp1, img2, kp2, matches[:50], None)
cv.imwrite("matches.jpg", vis)
14.2 用 RANSAC 估计单应性
import numpy as np
good = matches[:80]
pts1 = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(–1, 1, 2)
pts2 = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(–1, 1, 2)
H, mask = cv.findHomography(pts1, pts2, cv.RANSAC, 5.0)
单应性适合平面目标、文档、海报、图像拼接的局部场景。
15. 视频和摄像头
15.1 打开摄像头
import cv2 as cv
cap = cv.VideoCapture(0)
if not cap.isOpened():
raise RuntimeError("摄像头打开失败")
while True:
ok, frame = cap.read()
if not ok:
break
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 50, 150)
cv.imshow("frame", frame)
cv.imshow("edges", edges)
if cv.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv.destroyAllWindows()
15.2 读取视频文件
cap = cv.VideoCapture("input.mp4")
while True:
ok, frame = cap.read()
if not ok:
break
# process frame
cap.release()
15.3 写出视频
cap = cv.VideoCapture("input.mp4")
fps = cap.get(cv.CAP_PROP_FPS)
w = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
fourcc = cv.VideoWriter_fourcc(*"mp4v")
writer = cv.VideoWriter("output.mp4", fourcc, fps, (w, h))
while True:
ok, frame = cap.read()
if not ok:
break
writer.write(frame)
cap.release()
writer.release()
16. 人脸检测:Haar Cascade 入门例子
Haar Cascade 是传统方法,速度快但鲁棒性有限。现代项目通常会使用 DNN 或专门的人脸检测器。
import cv2 as cv
img = cv.imread("people.jpg")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cascade_path = cv.data.haarcascades + "haarcascade_frontalface_default.xml"
face_cascade = cv.CascadeClassifier(cascade_path)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
)
for x, y, w, h in faces:
cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv.imwrite("faces.jpg", img)
17. 相机标定的基本思路
相机标定用于估计内参和畸变参数。常见流程:
核心代码骨架:
import cv2 as cv
import numpy as np
from pathlib import Path
pattern_size = (9, 6)
square_size = 1.0
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(–1, 2)
objp *= square_size
objpoints = []
imgpoints = []
for path in Path("calib_images").glob("*.jpg"):
img = cv.imread(str(path))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ok, corners = cv.findChessboardCorners(gray, pattern_size)
if not ok:
continue
corners = cv.cornerSubPix(
gray,
corners,
(11, 11),
(–1, –1),
criteria=(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001),
)
objpoints.append(objp)
imgpoints.append(corners)
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv.calibrateCamera(
objpoints,
imgpoints,
gray.shape[::–1],
None,
None,
)
print(camera_matrix)
print(dist_coeffs)
18. DNN 模块简述
OpenCV 的 cv.dnn 可以加载一些深度学习模型做推理,例如 ONNX。
简化流程:
net = cv.dnn.readNetFromONNX("model.onnx")
blob = cv.dnn.blobFromImage(img, scalefactor=1/255.0, size=(640, 640), swapRB=True)
net.setInput(blob)
out = net.forward()
注意:
- swapRB=True 常用于把 OpenCV 的 BGR 转成模型需要的 RGB。
- 输入尺寸、归一化、均值方差必须和模型训练时一致。
- 后处理通常要自己写,比如 NMS、坐标反变换、类别映射。
19. 一个完整小项目:提取图片中的文档区域
目标:从图片中找到纸张/文档,做透视矫正。
import cv2 as cv
import numpy as np
def order_points(pts):
rect = np.zeros((4, 2), dtype=np.float32)
s = pts.sum(axis=1)
diff = np.diff(pts, axis=1).ravel()
rect[0] = pts[np.argmin(s)] # top-left
rect[2] = pts[np.argmax(s)] # bottom-right
rect[1] = pts[np.argmin(diff)] # top-right
rect[3] = pts[np.argmax(diff)] # bottom-left
return rect
img = cv.imread("document.jpg")
orig = img.copy()
ratio = img.shape[0] / 700.0
img = cv.resize(img, (int(img.shape[1] / ratio), 700))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = cv.GaussianBlur(gray, (5, 5), 0)
edges = cv.Canny(gray, 50, 150)
contours, _ = cv.findContours(edges, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv.contourArea, reverse=True)
doc = None
for cnt in contours:
peri = cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, 0.02 * peri, True)
if len(approx) == 4:
doc = approx.reshape(4, 2)
break
if doc is None:
raise RuntimeError("未找到文档四边形")
doc = doc * ratio
rect = order_points(doc)
width_a = np.linalg.norm(rect[2] – rect[3])
width_b = np.linalg.norm(rect[1] – rect[0])
height_a = np.linalg.norm(rect[1] – rect[2])
height_b = np.linalg.norm(rect[0] – rect[3])
max_w = int(max(width_a, width_b))
max_h = int(max(height_a, height_b))
dst = np.float32([
[0, 0],
[max_w – 1, 0],
[max_w – 1, max_h – 1],
[0, max_h – 1],
])
M = cv.getPerspectiveTransform(rect, dst)
warped = cv.warpPerspective(orig, M, (max_w, max_h))
cv.imwrite("document_warped.jpg", warped)
20. 常见坑
20.1 图片路径错误
cv.imread 失败时不会直接抛异常,而是返回 None。
img = cv.imread(path)
if img is None:
raise FileNotFoundError(path)
20.2 BGR/RGB 混淆
OpenCV 是 BGR,Matplotlib、PIL、很多深度学习模型是 RGB。颜色不对时先检查通道顺序。
20.3 dtype 和数值范围
uint8 图像范围是 0 到 255;深度学习或浮点计算常用 float32 和 0 到 1。混用时要明确转换。
img_float = img.astype(np.float32) / 255.0
img_uint8 = np.clip(img_float * 255, 0, 255).astype(np.uint8)
20.4 GUI 环境问题
服务器、Docker、远程终端里 cv.imshow 可能不可用。使用 headless 包时也没有 GUI 后端。此时用 cv.imwrite 保存结果,或用 Notebook/Matplotlib 显示。
20.5 视频编码问题
VideoWriter_fourcc 和输出容器要匹配。Windows、macOS、Linux 支持的编码器可能不同。遇到打不开的视频,尝试:
cv.VideoWriter_fourcc(*"mp4v")
cv.VideoWriter_fourcc(*"XVID")
cv.VideoWriter_fourcc(*"MJPG")
20.6 坐标和尺寸写反
图像数组 shape 是 (h, w),而很多 OpenCV API 的 size 是 (w, h)。
h, w = img.shape[:2]
resized = cv.resize(img, (w // 2, h // 2))
21. 学习路线
建议按这个顺序练:
22. 常用模板
22.1 批量处理文件夹图片
import cv2 as cv
from pathlib import Path
in_dir = Path("input_images")
out_dir = Path("output_images")
out_dir.mkdir(exist_ok=True)
for path in in_dir.glob("*.jpg"):
img = cv.imread(str(path))
if img is None:
print("skip:", path)
continue
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
out_path = out_dir / f"{path.stem}_gray.png"
cv.imwrite(str(out_path), gray)
22.2 保持比例缩放到指定宽度
def resize_width(img, width):
h, w = img.shape[:2]
scale = width / w
return cv.resize(img, (width, int(h * scale)), interpolation=cv.INTER_AREA)
22.3 画带文字的检测框
def draw_box(img, box, label, color=(0, 255, 0)):
x1, y1, x2, y2 = map(int, box)
cv.rectangle(img, (x1, y1), (x2, y2), color, 2)
cv.putText(img, label, (x1, max(0, y1 – 6)),
cv.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv.LINE_AA)
23. 推荐资料和来源
- OpenCV-Python 官方教程目录:https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html
- OpenCV 官方 pip 安装教程:https://docs.opencv.org/4.x/db/dd1/tutorial_py_pip_install.html
- OpenCV Windows 安装说明:https://docs.opencv.org/4.x/d5/de5/tutorial_py_setup_in_windows.html


