简介:这份资源面向机器视觉与人工智能方向的初学者及进阶开发者,围绕OCR、OpenCV与深度学习构建了一套可运行的人脸、视频与文字检测识别项目合集,帮助读者在真实代码中理解从环境搭建到模型推理的完整链路。包内共128个文件,以png截图、md教程文档、py脚本为主,另含gif演示、hdf5与h5模型权重、ttc字体及json配置,压缩包约35.56MB,兼顾代码、素材与说明。内容覆盖人脸检测与识别、轮廓标识、头像合成、数字化妆、性别与七种表情识别、视频对象提取、图片修复与自动上色、眼动追踪及换脸等实践方向,并配套OpenCV环境搭建、Tesseract OCR文字识别、Dlib与OpenCV双版本检测、Ubuntu与Windows源更换、OpenCV中文支持等教程。已有362人学习,适合按模块检索、对照复现并积累排错经验。
1. 从一张模糊的门禁抓拍说起:OCR、OpenCV 和人脸识别怎么串成一条流水线
小区门禁机拍到一张侧脸,光照不均、运动模糊,后台却要在 300 毫秒内完成人脸比对,同时把访客登记表上的姓名、身份证号用 OCR 抽出来入库。这个场景里,机器视觉负责“看清”,人工智能负责“看懂”,OpenCV 是那套趁手的图像处理工具箱,OCR 和人脸识别则是两条并行的识别支路。很多人把这几样东西当成四个独立项目分别学,结果真到落地时发现:摄像头取流、预处理、模型推理、结果结构化,任何一环掉链子整条线就废了。这篇笔记面向想用 Python 把 OpenCV、OCR、人脸检测识别串成一个可跑通项目的工程师,从环境装起,到参数怎么调、坑在哪,一步步拆开讲。适合有基础 Python 语法、想往机器视觉方向落地的读者,也适合正在做人工智能大作业或毕设选题的人拿来当骨架。
2. 环境与选型:OpenCV、OCR 引擎、人脸模型到底怎么搭
2.1 三个组件的职责边界先划清
动手之前必须把分工想明白,否则后面会写出“用 OCR 去识别人脸”这种荒唐代码。OpenCV 的定位是图像 I/O 和传统图像处理:读视频流、缩放、灰度化、直方图均衡、边缘检测、透视变换,它不负责“认识”图像内容。OCR 引擎负责把图像里的文字区域转成字符串,常见的有 Tesseract、PaddleOCR、ddddocr 这几类。人脸识别负责检测人脸框并对齐,再提取特征向量做比对,常见方案是 OpenCV 自带的 Haar/LBP 级联做检测,或者用 ONNX 格式的 ArcFace 类模型做特征提取。
选型上我的习惯是:检测用 OpenCV 的 DNN 模块加载轻量人脸检测模型,识别用 ArcFace 系列,OCR 中文场景优先 PaddleOCR,纯数字验证码场景用 ddddocr 更省事。热搜里常出现的modulenotfounderror: no module named 'opencv'和opencv安装成功却找不到cv2,九成是虚拟环境没激活或者 pip 装到了系统 Python 而 IDE 用的是另一个解释器,这个后面避坑章节细说。
2.2 用 conda 建一个干净环境并装齐依赖
不要直接在系统 Python 上 pip install,版本冲突会让你怀疑人生。下面这套命令我用了很多次,稳定。
# 创建独立环境,Python 版本选 3.9 或 3.10,兼容性最好 conda create -n vision_ocr python=3.10 -y conda activate vision_ocr # 装 OpenCV,用 opencv-python 而非 contrib 版,除非你要用 SIFT 等专利算法 pip install opencv-python==4.8.1.78 pip install opencv-contrib-python==4.8.1.78 # OCR 引擎:PaddleOCR 中文强,ddddocr 轻量 pip install paddlepaddle paddleocr pip install ddddocr # 人脸识别相关 pip install onnxruntime numpy pillow逻辑说明:opencv-python和opencv-contrib-python不要同时装两个不同版本,会互相覆盖导致cv2.error。PaddleOCR 首次运行会自动下载模型权重,需要网络通畅。onnxruntime用来跑 ArcFace 的 ONNX 模型,比直接装 PyTorch 轻量得多。
参数说明:OpenCV 版本我锁在 4.8.x,因为 4.4.0 在某些 Windows 编译版本上有已知的cv2.error路径问题,热搜里那条opencv(4.4.0) c:\users\appveyor\...报错就是典型。Python 选 3.10 是因为 PaddleOCR 对 3.11 的支持还不稳。
2.3 验证环境是否真的通了
装完别急着写业务代码,先跑一段最小验证,确认 cv2 能导入、能读图、OCR 能出字。
import cv2 import numpy as np # 打印版本,确认不是装了个假的 print("OpenCV version:", cv2.__version__) # 造一张纯色图,验证基本读写 img = np.zeros((200, 400, 3), dtype=np.uint8) img[:] = (255, 255, 255) cv2.putText(img, "TEST 123", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 0), 3) cv2.imwrite("test.png", img) # 读回来确认 back = cv2.imread("test.png") print("Image shape:", back.shape if back is not None else "read failed")逻辑说明:这段代码不依赖任何外部图片,自己造图自己读,能排除“图片路径错”的干扰。如果cv2.__version__打印出来但imread返回 None,说明 OpenCV 的编解码后端有问题,重装opencv-python通常能解决。
参数说明:np.zeros的 dtype 必须是uint8,OpenCV 只认这个。putText的坐标是左上角为原点,别写成数学坐标系。
3. 人脸检测与识别:从 Haar 到 ArcFace 的落地路径
3.1 检测环节:为什么我最终放弃了 Haar
Haar 级联是 OpenCV 自带、零依赖、跑得飞快,但它的误检率在复杂背景下高得离谱,侧脸和遮挡基本没戏。热搜里easyai人脸识别、arcface人脸识别这些词说明大家已经在往深度学习方案迁移。我的做法是:检测用 OpenCV DNN 加载一个轻量 SSD 或 YuNet 模型,识别用 ArcFace 提特征。
YuNet 是 OpenCV 官方在 4.5.4 之后集成的轻量人脸检测模型,模型文件只有几百 KB,CPU 上也能跑到实时。下面是从视频流里抓人脸框的最小实现。
import cv2 # 加载 YuNet 模型,模型文件需自行下载 onnx detector = cv2.FaceDetectorYN.create( model="face_detection_yunet_2023mar.onnx", config="", input_size=(320, 320), score_threshold=0.7, # 置信度阈值,低于此值的人脸丢弃 nms_threshold=0.3, # 非极大值抑制阈值,控制重叠框合并 top_k=5000 ) cap = cv2.VideoCapture(0) # 0 表示默认摄像头 while True: ret, frame = cap.read() if not ret: break h, w = frame.shape[:2] detector.setInputSize((w, h)) # 必须和实际帧尺寸一致 _, faces = detector.detect(frame) if faces is not None: for face in faces: x, y, fw, fh = face[:4].astype(int) cv2.rectangle(frame, (x, y), (x + fw, y + fh), (0, 255, 0), 2) cv2.imshow("face", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:setInputSize必须在每帧检测前调用,且要和帧的实际宽高一致,否则检测框坐标会整体偏移,这是新手最容易翻车的地方。score_threshold调高减少误检但会漏检,0.7 是室内场景的折中值。
参数说明:nms_threshold越小,重叠的人脸框合并越激进,多人脸靠近时容易漏掉一个,0.3 到 0.4 之间比较稳。top_k是保留的最大候选框数,一般用不到改。
3.2 识别环节:ArcFace 特征提取与比对
检测到人脸后,需要做对齐再提特征。ArcFace 的输入通常是 112x112 对齐后的人脸。对齐依赖五点关键点(双眼、鼻尖、嘴角),YuNet 的输出里正好带了这五个点,可以直接用。
import cv2 import numpy as np import onnxruntime as ort # 加载 ArcFace ONNX 模型 session = ort.InferenceSession("arcface_r100.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name def align_face(img, landmarks): # landmarks 是 5 个点的坐标,做相似变换对齐到标准位置 dst = np.array([[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041]], dtype=np.float32) src = np.array(landmarks, dtype=np.float32).reshape(5, 2) M, _ = cv2.estimateAffinePartial2D(src, dst) return cv2.warpAffine(img, M, (112, 112)) def get_embedding(face_img): # 归一化到 [-1, 1],ArcFace 的标准预处理 blob = cv2.dnn.blobFromImage(face_img, 1.0 / 128, (112, 112), (127.5, 127.5, 127.5), swapRB=True) emb = session.run(None, {input_name: blob})[0] # L2 归一化,方便用余弦相似度比对 return emb / np.linalg.norm(emb) # 比对:余弦相似度大于阈值判定为同一人 def cosine_sim(a, b): return float(np.dot(a, b.T))逻辑说明:estimateAffinePartial2D做的是相似变换,只含旋转、缩放、平移,不含剪切,这对人脸对齐足够。blobFromImage里的swapRB=True是因为 OpenCV 读进来是 BGR,而模型训练时用的是 RGB。
参数说明:ArcFace 的相似度阈值,同一人一般大于 0.5,不同人低于 0.3,中间地带需要根据业务调整。门禁场景宁可误拒不可误认,阈值可以设到 0.6。
3.3 把检测和识别串成一条完整链路
单独跑通检测和识别后,串起来才是项目。下面这段把摄像头、检测、对齐、特征提取、比对串成一条线,并维护一个简单的人脸库。
import cv2 import numpy as np import onnxruntime as ort import pickle import os # 初始化检测器和识别器 detector = cv2.FaceDetectorYN.create("face_detection_yunet_2023mar.onnx", "", (320, 320)) session = ort.InferenceSession("arcface_r100.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name # 人脸库:{姓名: 特征向量} DB_PATH = "face_db.pkl" face_db = pickle.load(open(DB_PATH, "rb")) if os.path.exists(DB_PATH) else {} def extract(frame, face): x, y, w, h = face[:4].astype(int) landmarks = face[4:14].reshape(5, 2) aligned = align_face(frame, landmarks) return get_embedding(aligned) cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break h, w = frame.shape[:2] detector.setInputSize((w, h)) _, faces = detector.detect(frame) if faces is not None: for face in faces: emb = extract(frame, face) name, best = "unknown", 0.0 for db_name, db_emb in face_db.items(): sim = cosine_sim(emb, db_emb) if sim > best: best, name = sim, db_name if best < 0.5: name = "unknown" x, y, fw, fh = face[:4].astype(int) cv2.rectangle(frame, (x, y), (x + fw, y + fh), (0, 255, 0), 2) cv2.putText(frame, f"{name} {best:.2f}", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("pipeline", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:人脸库用 pickle 存,简单够用。生产环境应该换成向量数据库,但做项目演示 pickle 完全够。best < 0.5判定为陌生人,这个阈值要结合你的实际数据调。
参数说明:face[4:14]是 YuNet 输出的五个关键点,顺序是右眼、左眼、鼻尖、右嘴角、左嘴角,和 ArcFace 标准模板的顺序要对应上,顺序错了对齐就废了。
4. OCR 文字识别:从截图到结构化字段的完整流程
4.1 OCR 引擎选型:PaddleOCR 和 ddddocr 各管一摊
热搜里php ocr识别验证码、c# ocr pdf、java使用百度ocr识别上传合同文件这些场景,本质是两类需求:一类是通用文档文字提取,一类是特定格式的验证码或票据。我的分工是:通用中文文档、表格、票据用 PaddleOCR,它的检测+识别+方向分类三件套开箱即用;纯数字字母验证码用 ddddocr,它专门为这个场景训练,准确率比通用引擎高一大截。
PaddleOCR 的调用极其简单,但首次运行会下载模型,网络不好会卡住。下面是最小可用代码。
from paddleocr import PaddleOCR # use_angle_cls=True 开启方向分类,处理倒置文字 # lang='ch' 中文模型,英文场景改 'en' 更快 ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False) result = ocr.ocr("invoice.png", cls=True) # result 结构:[[ [box, (text, confidence)], ... ]] for line in result[0]: box, (text, conf) = line print(f"文字: {text}, 置信度: {conf:.3f}, 位置: {box}")逻辑说明:ocr.ocr返回的是嵌套列表,第一层是图片(支持多图),第二层是文本行。每行包含四点坐标框、识别文字、置信度。show_log=False关掉 Paddle 的冗余日志,输出干净很多。
参数说明:use_angle_cls=True会多跑一个方向分类模型,速度慢约 20%,但能处理旋转文字,票据场景建议开。lang参数决定识别模型,中文用ch,纯英文用en速度更快。
4.2 用 OpenCV 做 OCR 前的预处理
OCR 引擎不是万能的,输入图像质量差,识别率断崖下跌。热搜里opencv图像处理项目、opencv识别物体说明大家已经在用 OpenCV 做前处理。我常用的三板斧是:灰度化、自适应二值化、形态学去噪。
import cv2 import numpy as np def preprocess_for_ocr(img_path): img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应二值化,应对光照不均 binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 10 ) # 开运算去噪点,闭运算连接断裂笔画 kernel = np.ones((2, 2), np.uint8) opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel) return closed processed = preprocess_for_ocr("invoice.png") cv2.imwrite("processed.png", processed)逻辑说明:adaptiveThreshold的 blockSize 必须是奇数,31 适合 A4 扫描件,小图要调小。开运算去掉孤立噪点,闭运算把断开的笔画连上,这两个顺序不能反。
参数说明:C值是从均值里减去的常数,值越大二值化越激进,文字容易断;值越小背景噪点越多。10 是扫描件的常用值,手机拍照场景可以调到 15。
4.3 从 OCR 结果里抽结构化字段
OCR 出来是一堆文本行,业务要的是“姓名、金额、日期”这些字段。热搜里java使用百度ocr识别上传合同文件时读取收入、单位、时间等关键字段就是这个需求。我的做法是用正则匹配加关键词定位。
import re def extract_fields(ocr_result): fields = {"name": None, "amount": None, "date": None} full_text = " ".join([line[1][0] for line in ocr_result[0]]) # 金额:匹配 ¥ 或 元 前后的数字 amount_match = re.search(r'[¥¥]?\s*(\d+[\.,]\d{2})\s*元?', full_text) if amount_match: fields["amount"] = amount_match.group(1).replace(",", "") # 日期:匹配 2024-01-01 或 2024年1月1日 date_match = re.search(r'(\d{4})[-年/](\d{1,2})[-月/](\d{1,2})', full_text) if date_match: fields["date"] = f"{date_match.group(1)}-{date_match.group(2)}-{date_match.group(3)}" # 姓名:定位"姓名"关键词后的 2-4 个中文字 name_match = re.search(r'姓名[::]?\s*([\u4e00-\u9fa5]{2,4})', full_text) if name_match: fields["name"] = name_match.group(1) return fields逻辑说明:先把所有文本行拼成一个长字符串,再做正则匹配,比逐行匹配更稳,因为 OCR 可能把“姓名”和值分到两行。[\u4e00-\u9fa5]是中文字符的 Unicode 范围。
参数说明:金额正则里的[\.,]兼容逗号和小数点两种分隔符。日期正则的[-年/]兼容三种常见格式。姓名限定 2 到 4 个字,太长会误匹配到地址。
5. 避坑与排查:那些让我加班到凌晨的报错
5.1 cv2 导入失败但明明装了
现象:import cv2报ModuleNotFoundError: No module named 'opencv',但pip list里能看到 opencv-python。
原因:IDE 用的解释器和 pip 装包的解释器不是同一个,或者虚拟环境没激活。热搜里opencv安装成功却找不到cv2就是这个。
解决:在代码里打印import sys; print(sys.executable),看实际用的 Python 路径,然后用这个路径对应的 pip 重装。conda 环境要先conda activate。
5.2 OpenCV 读视频流卡顿或花屏
现象:cap.read()返回的帧花屏,或者帧率极低。
原因:摄像头默认分辨率太高,或者解码后端不匹配。热搜里opencv 2.4.9 for linux这类老版本问题更多。
解决:显式设置分辨率和后端。cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)和cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480),后端用cv2.VideoCapture(0, cv2.CAP_DSHOW)在 Windows 上更稳。
5.3 OCR 识别不了韩文或小语种
现象:PaddleOCR 对韩文、日文识别率极低,热搜里以下ocr代码识别不了韩文就是这个。
原因:默认加载的是中文模型,不包含韩文字符集。
解决:PaddleOCR 支持多语言,初始化时lang='korean'或lang='japan',但模型需要单独下载。如果只是偶尔用,可以换 Tesseract 并装对应语言包。
5.4 人脸检测框整体偏移
现象:检测框画出来偏到人脸左上角或右下角。
原因:setInputSize没设,或者设的尺寸和实际帧不一致。YuNet 默认输入是 320x320,直接喂 640x480 的帧就会偏。
解决:每帧检测前调用detector.setInputSize((w, h)),w 和 h 从frame.shape取。
5.5 ddddocr 未安装或版本冲突
现象:ModuleNotFoundError: No module named 'ddddocr',或者装了但 import 报 onnxruntime 相关错误。
原因:ddddocr 依赖特定版本的 onnxruntime,和 ArcFace 用的版本可能冲突。
解决:给 ddddocr 单独建一个环境,或者用pip install ddddocr --no-deps再手动装兼容的 onnxruntime。热搜里ddddocr 未安装多半是网络问题导致 pip 没装完。
6. 进阶技巧:用 OpenCV 的 solvePnP 做头部姿态估计
人脸识别跑通后,如果想判断用户是不是在“正视屏幕”,可以加一步头部姿态估计。热搜里opencv的函数solvepnp正好是这个用途。原理是用人脸的几个 3D 模型点和 2D 图像点做对应,解出旋转向量,再转成欧拉角。
import cv2 import numpy as np # 3D 人脸模型点(通用人脸比例,单位任意) model_points = np.array([ (0.0, 0.0, 0.0), # 鼻尖 (0.0, -63.6, -12.5), # 下巴 (-43.3, 32.7, -26.0), # 左眼左角 (43.3, 32.7, -26.0), # 右眼右角 (-28.9, -28.9, -24.1), # 左嘴角 (28.9, -28.9, -24.1) # 右嘴角 ], dtype=np.float64) def estimate_head_pose(landmarks_2d, img_size): # landmarks_2d 是 6 个点的图像坐标 focal_length = img_size[1] center = (img_size[1] / 2, img_size[0] / 2) camera_matrix = np.array([ [focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1] ], dtype=np.float64) dist_coeffs = np.zeros((4, 1)) # 假设无畸变 success, rot_vec, trans_vec = cv2.solvePnP( model_points, landmarks_2d, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE ) if not success: return None # 旋转向量转旋转矩阵再转欧拉角 rot_mat, _ = cv2.Rodrigues(rot_vec) angles, _, _, _, _, _ = cv2.RQDecomp3x3(rot_mat) return angles # [pitch, yaw, roll]逻辑说明:solvePnP解的是透视 n 点问题,给定 3D 点和对应 2D 点,求相机外参。SOLVEPNP_ITERATIVE适合点数少的情况,点数多可以用SOLVEPNP_EPNP更快。RQDecomp3x3把旋转矩阵分解成欧拉角,返回的 angles 里 yaw 超过 ±15 度基本可以判定为侧脸。
参数说明:focal_length用图像宽度近似,精度要求高的话需要相机标定。dist_coeffs全零是简化处理,广角镜头必须做畸变校正否则角度偏差大。
验证方法:让测试者正对摄像头,看 yaw 是否接近 0;头往左偏,yaw 应该往一个方向变。如果方向反了,检查 model_points 的坐标系定义。
我自己的习惯是,任何姿态估计的代码写完,先拿自己的脸在摄像头前转一圈,把三个角度的变化范围记下来,再定阈值。这比看论文里的公式快得多。希望帮到你。
本文还有配套的精品资源,点击获取