ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于OpenCV的笔迹识别:预处理、特征提取与相似度判定

基于OpenCV的笔迹识别:预处理、特征提取与相似度判定 简介图像识别作为计算机视觉的基础分支在身份验证、文档分析等场景中具有广泛应用。传统图像处理技术通过特征工程而非深度学习方法即可在小样本条件下实现有效的模式比对。OpenCV作为开源视觉库提供了丰富的图像预处理、形态学操作和特征描述工具使得笔迹识别这类任务能够以轻量级、可解释的方式落地。围绕笔迹识别系统文章详细阐述从灰度化、二值化、骨架提取到HOG与GLCM特征融合的完整链路并给出相似度计算与阈值判定的实现方案。该方法适合课程设计、毕业答辩及快速原型验证兼顾效果与工程可实施性为理解传统图像识别技术提供了一套完整的实践路径也展示了OpenCV在笔迹比对中的实际应用价值。1. 基于OpenCV的笔迹识别先搞清楚你在做什么题一个课程设计或者毕业设计的常见场景手里拿到若干张笔迹扫描件有的是手写汉字、有的是签名需要判断这些字是不是同一个人写的。用深度学习做样本不够、训练时间也不允许纯靠肉眼比对又没法在答辩时讲出一套可视化的判定依据。于是“基于OpenCV图像识别的笔迹识别系统”就成了一个很自然的折中方案——不依赖GPU不用标注几百张图靠图像预处理加特征比对就能把“像不像”变成一个可量化的相似度分数。这套路的本质不是让计算机“理解”笔迹而是把笔迹转成一组可比较的数值特征在已知书写者的小样本集合里做判定。它适合课程设计、毕设起步也适合想快速验证笔迹比对思路的从业者但先泼一盆冷水它做不了司法鉴定级的人笔迹认定定位是“教学演示 小规模验证”级别的完整闭环系统。下面按我实际做过的方案把这套系统的原理、代码、参数和坑从头讲一遍。2. 系统架构与识别流程先立住技术骨架2.1 为什么是OpenCV而不是深度学习这个标题里写的是“基于opencv图像识别”那就要先把这个选型理由讲透。我见过不少同学拿到这类题目第一反应是“现在图像识别不都用深度学习吗”然后上来就想用CNN。但笔迹识别这个场景有几个现实约束第一数据量不够。深度学习做图像分类一个类目几十张图根本训不出稳定的模型而笔迹识别项目通常能拿到的就是几个人、每人几篇字。OpenCV这条路是“特征工程 传统机器学习距离判定”对样本量要求低5个人每人5份样本就能搭出一个能演示的比对系统。第二可解释性好。答辩时要回答“你怎么判断这是同一个人的字”用OpenCV可以讲出“我提取了笔画的端点、分叉点数量计算了方向梯度直方图然后算特征向量的余弦相似度”每一步都能画出中间结果。换成卷积网络就只能说“模型自动提取了深层特征”说服力反而弱。第三性能开销低。OpenCV的预处理和特征提取跑在一张普通的CPU上处理一张512x512的笔迹图只要几百毫秒不会出现在答辩现场等程序转圈的尴尬。顺带说一句有些工业场景会拿OpenCV和Halcon、Vision Master这类商业视觉工具做对比笔迹识别这种教学演示规模的项目OpenCV完全够用而且OpenCV的跨平台特性让整套代码在Windows和Linux都能跑部署成本低。学习阶段建议多参考一些成体系的OpenCV例程像youcans的OpenCV系列就覆盖了从像素操作到特征匹配的完整路径适合作为函数级参考。2.2 识别管线五个环节缺一不可我做这套系统时把流程固定成五个环节每个环节一个Python模块方便单独调试图像采集与读取→预处理→特征提取→特征比对→结果输出采集环节负责读取扫描件或手拍照片这一步最常见的坑就是中文路径和图像方向问题后面避坑章节细说。预处理环节解决“把纸面上的字变成干净的图像”这件事包括灰度化、二值化、去噪、归一化尺寸。特征提取是这个系统的核心我选择了“结构特征 统计特征”的组合结构特征描述笔画的形态统计特征描述笔画的纹理分布。比对环节用余弦相似度和欧氏距离综合打分超过阈值判为同一人。结果输出可以是一个命令行打印也可以做成一个简单的可视化界面。这个管线里最容易被忽视的是预处理对最终效果的影响权重。我自己的经验预处理至少贡献了70%的效果。特征提取算法大家都差不多但预处理不到位比如二值化阈值没调好导致笔画断裂后续提取的端点数量就会偏大整个特征向量都失真。所以在这个项目里花时间调预处理参数是最划算的投入。2.3 开发环境搭建与目录组织先给出我惯用的环境配置方式。这个标题的配套交付里一般会自带部署说明文档但环境配置这东西每台机器都不一样部署说明只能覆盖通用情况本地装环境还是得自己动手。推荐直接用Anaconda建一个虚拟环境避免污染系统Pythonconda create -n handwriting python3.9 -y conda activate handwriting pip install opencv-python4.8.1.78 pip install numpy1.24.3 pip install scikit-learn1.3.0 pip install scikit-image0.21.0 pip install matplotlib3.7.2这里对几个包做个说明opencv-python是预编译的OpenCV标准版自带cv2模块包含图像读取、滤波、形态学操作等核心功能不需要自己去CMake编译scikit-image用于骨架提取它的skeletonize函数是成熟实现比自己手写Zhang-Suen细化算法稳得多scikit-learn用来做特征标准化和距离计算比手写循环快而且不容易出边界错误。很多人在安装opencv这一步就翻车最常见的是ModuleNotFoundError: No module named cv2。这通常有三个原因一是当前终端激活的虚拟环境和安装包的虚拟环境不是同一个在Anaconda Prompt里先确认conda activate handwriting执行成功二是用了pip install opencv这种错误包名正确包名是opencv-python三是镜像源同步问题可以加上-i https://pypi.tuna.tsinghua.edu.cn/simple走国内镜像。我之前在Anaconda Prompt里面没有opencv排查半天最后发现是同时装了Anaconda和系统Python终端默认走的是系统Python的pip装上之后自然看不到。解决办法是直接用python -m pip install opencv-python指定当前解释器。目录结构我会这样组织handwriting_recognition/ ├── src/ │ ├── preprocess.py # 预处理模块 │ ├── features.py # 特征提取模块 │ ├── matcher.py # 比对判定模块 │ ├── main.py # 主程序入口 ├── data/ │ ├── raw/ # 原始笔迹图像 │ ├── processed/ # 预处理后的中间结果 │ └── samples/ # 测试样本 ├── docs/ │ └── design.md # 设计文档 └── requirements.txt为什么要在源码之外单独保留processed目录因为预处理中间结果可以可视化输出成图片做PPT演示的时候直接贴出来比干讲算法流程图有说服力得多。这也是答辩演示时的一个加分细节。3. 数据准备与预处理识别效果七成取决于这一步3.1 采集规范和样本增强数据采集的规范直接影响后面所有环节。笔迹识别的前提是“同一个人的笔迹存在可重复的形态特征”但如果采集条件不统一系统把采集背景差当成笔迹差就会出现误判。我建议按这个标准收集样本采集项推荐参数说明纸张白色A4纸无格线格线会干扰二值化检测出的边缘不全是笔画书写工具黑色签字笔0.5mm避免圆珠笔反光和铅笔灰度波动采集设备300dpi扫描仪优先手机拍摄要正拍、补光均匀内容固定文字段落同一段文字才方便做局部特征比对样本量每人至少5份3份建库2份做测试样本增强不是必须的但如果只有3、4份样本我建议做简单的几何增强旋转正负5度、轻微缩放0.95到1.05倍、微小平移几个像素。注意增强只用于扩充特征库不要拿增强样本当测试集否则识别结果虚高答辩时一追问就露馅。3.2 预处理主流程实现预处理模块的核心逻辑在这里这张代码是整套系统的基础后面所有特征都基于它的输出import cv2 import numpy as np def preprocess_pipeline(image_path, output_size(512, 512)): # 1. 读取图像并转为灰度图 img cv2.imread(image_path) if img is None: raise ValueError(f无法读取图像: {image_path}) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 高斯模糊抑制扫描噪点 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 3. 自适应阈值二值化笔画变白、背景变黑 binary cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, blockSize35, C10 ) # 4. 形态学开运算消除小噪点 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) cleaned cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 5. 缩放并填充到统一尺寸 resized cv2.resize(cleaned, output_size, interpolationcv2.INTER_NEAREST) return resized, gray重点说几个参数的选择逻辑。用的是adaptiveThreshold而不是全局threshold原因在于不同扫描件的亮度分布不均匀全局阈值会把亮区域的浅笔迹滤掉自适应阈值用局部均值做基准抗光照变化能力强。blockSize35是经验值它定义了局部区域大小块太小时笔画内部会被误判为背景变成空洞块太大时又退化成全局阈值C10是阈值偏移量C值越大保留的笔画越少C值越小噪声越多。形态学的(3, 3)矩形核对笔迹这种细长结构是安全的不会破坏笔画主干但如果核太大比如(5, 5)就会把细笔画直接抹掉。缩放用INTER_NEAREST而不是默认的INTER_LINEAR是为了保持二值图像的锐利边缘。线性插值会产生灰度过渡像素导致后续骨架提取出现毛刺。这个细节值得记住。3.3 骨架提取与重心对齐预处理之后得到的是一张“白色笔画、黑色背景”的二值图但如果直接用这张图做特征提取笔画宽度本身会成为一个干扰因素——同样是写“永”字笔粗的人和中细笔的人画出的前景面积差很多。骨架提取就是把笔画“瘦身”成单像素宽的线条保留拓扑结构去除宽度信息。我用了scikit-image的skeletonize它是形态学细化算法的成熟实现比自己写迭代删除像素的算法稳定from skimage.morphology import skeletonize import cv2 import numpy as np def extract_skeleton(binary_image): # skeletonize 要求输入是0/1二值图0为背景1为前景 skeleton skeletonize(binary_image.astype(np.uint8) // 255) skeleton_img (skeleton * 255).astype(np.uint8) # 重心对齐让笔迹主体居中减少位置偏移对特征的影响 moments cv2.moments(skeleton_img, binaryImageTrue) if moments[m00] ! 0: cx int(moments[m10] / moments[m00]) cy int(moments[m01] / moments[m00]) center (skeleton_img.shape[1] // 2, skeleton_img.shape[0] // 2) dx center[0] - cx dy center[1] - cy matrix np.float32([[1, 0, dx], [0, 1, dy]]) skeleton_img cv2.warpAffine( skeleton_img, matrix, (skeleton_img.shape[1], skeleton_img.shape[0]), borderValue0 ) return skeleton_img, moments骨架提取后做重心对齐是很多第一次做笔迹识别的人容易漏掉的步骤。如果两张待比对的笔迹图一张字迹偏左上、一张偏右下就算拿到同样的特征提取算法算出来的特征分布也会错位。用cv2.moments算出前景像素的质心再通过平移变换把质心移到图像中心相当于先做了一次“对齐”把位置信息从特征里剔除。borderValue0保证平移后露出的背景区域是黑色不会引入额外白色像素干扰特征统计。这段代码输出的是骨架图建议在调试时用cv2.imwrite()把每张骨架图存下来看一眼。正常情况下笔画应该是连续的单像素线条如果出现大量断裂说明预处理阶段的自适应阈值参数有偏差要回到上一步去调blockSize和C而不是继续往下走。这种检查习惯能帮你省下大量无意义的调参时间。4. 特征提取与笔迹比对核心算法实现4.1 特征方案结构特征加统计特征骨架提取完之后要回答的核心问题是用哪些数值来描述“一个人的笔迹风格”。纯用像素级特征比如计算前景像素占比区分度太低不同人的笔迹可能占比相似纯用深度学习特征又回到了数据量不够的老问题。我的方案是组合两类特征。结构特征描述笔画的几何形态端点数、分叉点数、环数、笔画密度分布。这些特征和人的书写习惯强相关——有人收笔利落所以端点干净有人喜欢连笔所以分叉点多。统计特征描述笔画的纹理模式灰度共生矩阵GLCM和方向梯度直方图HOG。GLCM捕捉笔画内部的纹理规律HOG捕捉笔画边缘的方向分布。笔画方向分布其实是笔迹识别里最有区分度的特征之一写横平竖直的人和写斜势连绵的人HOG特征分布一眼就能分辨。特征类别具体特征维度说明结构特征端点数1笔画孤立末端的数量结构特征分叉点数1笔画分岔处的数量结构特征环数1闭合区域的个数结构特征8x8网格密度64各区域骨架像素占比统计特征HOG144方向直方图9方向x16块统计特征GLCM对比度/能量/相关性3灰度共生矩阵统计量这个特征组合的维度是214维对于小样本的笔迹比对场景维度适中不需要再做降维。如果样本量特别大可以考虑PCA降维到50维再用但在这个项目规模上没必要。4.2 特征提取代码实现特征提取模块是整套系统信息密度最高的部分我把实现拆成两个函数import cv2 import numpy as np from skimage.feature import local_binary_pattern, graycomatrix, graycomatrix_props def extract_structural_features(skeleton_img): # 用图像连通域分析找端点和分叉点 # 方法对每个骨架像素统计其8邻域内白色像素的数量 # 邻域为1个像素 - 端点邻域为3个及以上 - 分叉点 skeleton_bin (skeleton_img 0).astype(np.uint8) h, w skeleton_bin.shape kernel np.ones((3, 3), np.uint8) neighbor_count cv2.filter2D(skeleton_bin.astype(np.float32), -1, kernel) neighbor_count np.round(neighbor_count).astype(np.int32) # 中心像素本身会被计入邻域减1得到实际邻域前景数 neighbor_count neighbor_count * skeleton_bin endpoints np.sum(neighbor_count 2) # 端点 branches np.sum(neighbor_count 4) # 分叉点 # 环数用连通域和孔洞计数近似 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(skeleton_bin) hole_count num_labels - 1 # 粗略估计 # 8x8网格密度特征 grid_h, grid_w 8, 8 cell_h, cell_w h // grid_h, w // grid_w density [] for i in range(grid_h): for j in range(grid_w): cell skeleton_bin[i*cell_h:(i1)*cell_h, j*cell_w:(j1)*cell_w] density.append(np.sum(cell) / (cell_h * cell_w)) return np.array([endpoints, branches, hole_count] density, dtypenp.float32) def extract_texture_features(gray_img): # GLCM特征角度为0度距离为1 glcm graycomatrix( gray_img, distances[1], angles[0], levels256, symmetricTrue, normedTrue ) contrast graycomatrix_props(contrastglcm)[0][0] energy graycomatrix_props(energyglcm)[0][0] correlation_val graycomatrix_props(correlationglcm)[0][0] # HOG特征OpenCV内置实现 hog cv2.HOGDescriptor( _winSize(64, 64), _blockSize(16, 16), _blockStride(8, 8), _cellSize(8, 8), _nbins9 ) # 输入灰度图需要先放缩到winSize resized_gray cv2.resize(gray_img, (64, 64), interpolationcv2.INTER_AREA) hog_feature hog.compute(resized_gray).flatten() return np.concatenate([np.array([contrast, energy, correlation_val]), hog_feature])这段代码里用到了一个计算邻域统计的小技巧cv2.filter2D配合3x3的全1卷积核可以一次算出每个像素周围的前景像素数量。卷积结果再乘上neighbor_count自身保证只有前景像素参与计数。端点判断neighbor_count 2的意思是除自己之外周围只有一个前景像素分叉点判断neighbor_count 4是周围至少3个方向有笔画延伸。这里要特别注意skeletonize输出的骨架是单像素宽的邻域统计才有意义如果拿原始二值图做统计笔画内部的像素周围全是前景根本分不出端点。HOG参数里_winSize(64, 64)要求输入图像正好是这个尺寸所以先用cv2.resize把灰度图放缩到64x64。块大小16x16、块步长8x8、细胞元8x8、直方图通道数9这个组合是行人检测的标准配置套在笔迹上表现也不错原因是笔画边缘的方向分布主要靠HOG的梯度方向精度而这个精度由nbins控制9个方向恰好覆盖0到180度。4.3 相似度计算与阈值判定特征提取完比对就简单了。对两个特征向量我同时算余弦相似度和欧氏距离加权综合出最终相似度from sklearn.preprocessing import normalize import numpy as np def combine_features(struct_feat, texture_feat): return np.concatenate([struct_feat, texture_feat]) def compare_handwriting(feat1, feat2): # 标准化每个维度均值为0方差为1 combined np.vstack([feat1, feat2]) mean np.mean(combined, axis0) std np.std(combined, axis0) 1e-6 norm1 (feat1 - mean) / std norm2 (feat2 - mean) / std # 余弦相似度 cos_sim np.dot(norm1, norm2) / (np.linalg.norm(norm1) * np.linalg.norm(norm2) 1e-6) # 欧氏距离转化为0~1相似度 euclidean_dist np.linalg.norm(norm1 - norm2) euclidean_sim 1.0 / (1.0 euclidean_dist) # 加权融合余弦占0.6欧氏占0.4 final_score 0.6 * cos_sim 0.4 * euclidean_sim return final_score, cos_sim, euclidean_sim def decide_match(score, threshold0.72): return 同一人书写 if score threshold else 不同人书写std加1e-6是为了防止某维特征在所有样本上都是同一个值比如所有样本的环数都是0此时标准差为0除零会报错。加权融合的比例是我试验后觉得相对均衡的余弦相似度对向量的方向变化敏感适合捕捉笔画风格差异欧氏距离对绝对数值差异敏感适合捕捉笔画浓淡、密度的差异。如果只用一个要么对笔迹整体形态变化不敏感要么对噪声过于敏感。阈值的确定不是拍脑袋需要留出一个“不确定区间”。我一般会在构建特征库时记录同一人的相似度分布和不同人的相似度分布把阈值画在两者分布的交界处。如果两份手写字的得分是0.7左右系统应提示“存疑”而不是硬判“同一人”。这个策略在答辩时很加分展示了对系统边界的认知。主流程的串接逻辑import os import numpy as np def build_feature_library(person_samples): # person_samples: {张三: [path1, path2, path3], 李四: [...]} library {} for person, paths in person_samples.items(): feats [] for p in paths: cleaned, gray preprocess_pipeline(p) skeleton, _ extract_skeleton(cleaned) struct extract_structural_features(skeleton) texture extract_texture_features(gray) feats.append(combine_features(struct, texture)) library[person] np.mean(feats, axis0) # 每个人存均值特征 return library def identify_probe(probe_path, library): cleaned, gray preprocess_pipeline(probe_path) skeleton, _ extract_skeleton(cleaned) struct extract_structural_features(skeleton) texture extract_texture_features(gray) probe_feat combine_features(struct, texture) scores {} for person, ref_feat in library.items(): scores[person] compare_handwriting(probe_feat, ref_feat)[0] best_person max(scores, keyscores.get) return best_person, scores特征库存的是每个书写者的均值特征向量好处有两点一是入库的多份样本不用全部保留特征节省内存二是均值起到了平滑噪声的作用单份样本的某个特征偶发异常不会严重影响匹配结果。识别阶段拿测试样本的特征和库里的每个人逐一比对得分最高且超过阈值的人就是结果如果最高分也没过阈值输出“库中无匹配”这是很重要的一个分支很多新手写的程序只输出“最像的人”没有“谁都不像”的选项逻辑上是缺失的。5. 避坑清单OpenCV笔迹识别项目常见的5个坑这个项目看起来链路不长但每个环节都有一些经验型的问题。下面按我遇到的频率排序列出每条都是“现象 → 原因 → 解决”的结构。5.1 安装opencv后import cv2报错现象在终端里执行python输入import cv2抛出ModuleNotFoundError: No module named cv2。很多人的第一反应是重装opencv但常常重装多次无果。原因99%的情况是环境混乱。你在Anaconda Prompt里用conda activate切换到了虚拟环境A但pip和python解释器指向的根本不是同一个环境。还有一部分情况是装了opencv-contrib-python之后又装opencv-python两个包冲突导致cv2加载异常。解决先执行conda env list确认当前环境再执行python -c import sys; print(sys.executable)确认解释器路径两者应该指向同一个目录。安装时统一用python -m pip install opencv-python不用裸的pip命令。如果不小心同时装了contrib版和标准版先全部卸载再重装一个版本。如果conda源里装的是老版本可以指定版本号安装比如针对Python 3.9用opencv-python 4.8.x是稳定的组合。5.2 cv2.imread读不了中文路径图片现象代码在Windows上跑图片路径里包含“张三”“样本”等中文字符时cv2.imread返回None但同样的代码在macOS上正常。原因OpenCV的imread底层用的是C标准库的文件读取接口在Windows上不支持UTF-8编码的路径字符串中文路径直接读取失败。这个问题在Windows上存在很多年了属于OpenCV的历史遗留问题。解决两步走。第一项目目录尽量全英文样本文件名用zhangsan_01.png而不是张三_第1张.png第二如果确实要读中文路径用np.fromfile加cv2.imdecode替代cv2.imreadimport cv2 import numpy as np def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) if data.size 0: raise ValueError(f文件不存在或为空: {path}) return cv2.imdecode(data, cv2.IMREAD_COLOR)这个写法在Windows和Linux上都能稳定工作建议直接封装成通用工具函数整套系统的图像读取统一走它。5.3 二值化后笔画断裂特征完全失真现象预处理输出图上原本连续的笔画出现了大量断点看起来像虚线。骨架提取后端点数量暴增同一个人的两份样本质心对不齐匹配得分急剧下降。原因全局阈值cv2.threshold对光照不均匀的图像处理结果差或者自适应阈值的blockSize设置太小/C值设置过大把浅色笔画部分误判为了背景。解决优先用自适应阈值。如果已经用了自适应阈值还是断裂把blockSize调大例如从15改成35把C值调小从15改成5。另外可以增加一步闭运算先膨胀后腐蚀来修复断裂kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)闭运算可以把间距较小的断点桥接起来但注意核不能太大否则会把笔画缝隙也填上。调参的时候建议实时保存中间结果图用肉眼确认断裂问题是否缓解参数调整才有依据。5.4 不同扫描分辨率导致特征数值差异巨大现象训练样本是300dpi扫描的测试样本是手机拍的或150dpi扫描的同一人的笔迹比对得分反而低于不同人的得分。结构特征里的端点和分叉点数量完全对不上。原因分辨率影响笔画宽度和细节保留程度。300dpi下笔画的锯齿边缘被清晰记录毛刺被骨架提取判定为分叉点150dpi下笔画边缘平滑分叉点少。这个差异直接污染了结构特征。解决在预处理阶段统一分辨率先按比例缩放到固定宽度。具体做法是在preprocess_pipeline里用cv2.resize把图像长边缩放到1024像素再进行后续处理。注意缩放要用INTER_AREA区域插值它对图像内容的保留最好。不同来源的图像应该统一采样不能一份300dpi一份150dpi直接送进特征提取。5.5 骨架提取产生毛刺端点统计虚高现象骨架图上的主干笔画旁边伸出短线毛刺像仙人掌上的刺。端点数严重偏大明显不符合人眼看到的笔画结构。原因骨架提取算法对边缘噪声敏感。二值化图像如果存在轻微锯齿细化过程中这些锯齿会被保留成短分支。毛刺本质上是边缘噪声的骨架化结果。解决骨架提取前先对二值图做一次中值滤波或者先做一次开运算消除孤立噪点。如果骨架提取后毛刺仍然存在可以用一个后处理步骤遍历骨架图删除长度小于min_length经验值5~8像素的分支线段。用cv2.connectedComponentsWithStats找出所有连通区域面积小于阈值的连通域直接置为背景num_labels, labels, stats, _ cv2.connectedComponentsWithStats(skeleton_img, connectivity8) for label_id in range(1, num_labels): if stats[label_id, cv2.CC_STAT_AREA] 8: skeleton_img[labels label_id] 0这个处理对主干较长、分支毛刺很短的情况非常有效。但注意阈值不能设太大否则会误删真实存在的短笔画比如“一”字本身就短面积阈值设到20可能整条都被删掉。具体数值需要根据你的图像分辨率在调试时观察确定。6. 部署验证与答辩演示从能跑到能讲系统跑通之后验证和展示才是决定这个项目最终评价高低的关键。建议做一个完整的最小演示脚本既能自测效果也可以在答辩现场直接展示。功能包括读入一份测试笔迹和特征库里的所有样本逐一比对输出得分排序和判定结果import argparse def main(): parser argparse.ArgumentParser(description基于OpenCV的笔迹识别系统) parser.add_argument(--probe, requiredTrue, help待识别笔迹图像路径) parser.add_argument(--library, default./data/samples, help特征库样本目录) parser.add_argument(--threshold, typefloat, default0.72, help判定阈值) args parser.parse_args() # 特征库构建示例逻辑实际按你的目录结构调整 person_samples load_sample_paths(args.library) library build_feature_library(person_samples) best_person, scores identify_probe(args.probe, library) print(比对得分) for person, score in scores.items(): print(f {person}: {score:.4f}) if scores[best_person] args.threshold: print(f判定结果该笔迹属于【{best_person}】) else: print(判定结果特征库中无匹配)验证方法上我建议做“留一法”每个人留一份样本不进入特征库用它做测试遍历所有样本得到识别准确率。如果5个人每人5份样本留一法就是跑25次识别这套流程在CPU上几分钟就能跑完。用表格记录每次识别的得分分布答辩时贴上这个表比任何算法描述都有说服力。如果想把系统做得更完整可以在特征层面加入一个简单的中值滤波降噪实验对比或者加入不同书写工具的对比测试这些都能体现出你对系统边界的思考。进阶方向可以提一下如果想提高识别精度当前这套结构的自然演化是替换特征提取环节用预训练的CNN模型提取深层特征然后做距离比对但代价是引入了深度学习依赖和更长的推理时间对课程设计来说不是必选项。我自己的做法是先确保OpenCV这条链路每个环节都能用可视化结果讲清楚再决定要不要加深度学习的对比实验。最后说句实在话这类项目真正的难点从来不是某个算法有多先进而是每个环节的参数是否经得起推敲、每个输出能否自圆其说把过程记录好、把坑填平这就是一个让人信服的完整系统。希望帮到你。本文还有配套的精品资源点击获取
返回列表