简介一套基于Python与OpenCV的印章识别完整项目源码主要服务于计算机相关专业在校生的毕业设计、课程设计与项目演示。代码经过本地编译运行评审分达95分并配有使用文档帮助学习者快速掌握印章检测、轮廓提取与匹配识别的实现路径。整个压缩包共93个文件约24.74MB包含核心Python识别脚本及配套Java/Gradle工程同时有XML/JSON配置文件、PNG/JPG样本图像和PDF说明文档便于对照调试与二次开发。目前已有176人学习下载具有一定参考热度。目录按主工程、后端服务与配置分层组织使用文档说明了环境配置与运行步骤打包模块可支持整体迁移适合在此基础上修改扩展其他图像识别功能。对于需要快速搭建印章识别原型或在OpenCV图像处理方向深入进阶的开发者是一份实用性很强的参考资料。1. 印章识别不是“读文字”先解决印章在哪、是什么形状拿到一摞合同扫描件要自动登记盖章单位很多人第一反应是上OCR。但真正用PythonOpenCV做印章识别程序时会发现70%以上的工作量花在“找印章”而不是“读印章”上。A4扫描件里那张圆形红色印痕会被背景里的红头文件、发票专用章、手写红字反复干扰定位不准后面OCR识别出来的就是一堆乱码。这篇笔记从一个可运行的印章识别项目出发把轮廓检测、颜色分割、霍夫圆变换和OCR识别串成一条完整链路讲清楚每一步的选型理由和参数怎么调。适合正在做课程设计或毕业设计选型的学生也适合需要批量归档合同、发票的自动化脚本工程师。不需要GPU普通电脑加Python环境就能跑通。2. 环境准备与项目结构跑通OpenCV项目的最小配置在打开代码之前先把环境问题说清楚。这类项目的源码包一般会带README和使用文档但很多人卡在第一步依赖装不上或者版本不对导致import cv2就报错。这里给一套我常用的、在Windows和macOS上都能复现的配置方案。2.1 为什么选OpenCV而不是直接上深度学习印章识别的目标物有一个非常强的先验红色、圆形或椭圆形、内部有文字和五角星。这个先验用传统OpenCV图像处理项目就能吃得很透不一定要标注几百张图去训练目标检测模型。用YOLO这类检测模型是更“现代化”的方案它能区分合同上的红头文件标题和真正的公章抗干扰能力强在小数据集上也能微调。但代价是你要先整理数据集、标注边界框、训练、验证整个流程至少一周起步。而印章外观高度标准化颜色和形状是强特征OpenCV的颜色分割加轮廓筛选一天内就能出第一版结果。我一般这样分工OpenCV负责定位圆形红色区域OCR负责识别里面的文字。定位准了OCR的输入就是一个干净、水平、放大的ROI区域识别率远高于对整张扫描件做OCR。这也是这个项目叫“印章识别程序”而不是“OCR程序”的原因——真正的核心是检测不是读字。2.2 从零搭建PythonOpenCV环境安装Python时选3.9到3.11之间的版本不要追最新。原因很现实PaddleOCR、pytesseract这些识别库对新版Python的支持会滞后经常出现“No module named paddle”或编译错误。python安装教程到处都是但虚拟环境这一步经常被跳过导致后续库冲突。# 创建虚拟环境项目依赖都装在这里面 python -m venv sealenv # Windows 激活 sealenv\Scripts\activate # macOS / Linux 激活 source sealenv/bin/activate # 安装OpenCV基础库与图像处理辅助库 pip install opencv-python opencv-contrib-python numpy matplotlib # 按需安装OCR库本项目的文字识别环节用 pip install paddlepaddle paddleocr说明opencv-python是预编译的标准库opencv-contrib-python包含SIFT等扩展模块。本项目用到的基础轮廓检测、颜色转换只在标准库里装第一个就够第二个主要是为了后面调试方便不装也不影响主流程。paddlepaddle是PaddleOCR的深度学习框架底座。如果你选择Tesseract路线则用pip install pytesseract但还要单独安装tesseract二进制程序。后面第4章我会展开对比。验证环境是否可用用下面这段import cv2 import numpy as np print(cv2.__version__) # 出现 4.x.x 即成功 print(np.__version__)如果import cv2报ModuleNotFoundError先确认当前在哪个环境里执行再看pip list里有没有opencv-python。这类问题九成是虚拟环境和系统Python混用了。2.3 一个印章识别项目的典型文件布局拿到“源码使用文档全部资料”这样的压缩包不要急着双击main.py。先用文件管理器理清目录结构心里有数再跑。一个标准的OpenCV课程设计项目通常按职责拆成四个模块文件/目录职责典型输出config.py集中存放所有可调参数颜色阈值、轮廓面积范围、路径utils.py图像预处理公共函数灰度化、形态学操作、保存中间图seal_detect.py印章定位检测返回印章ROI坐标、掩码seal_ocr.py文字识别与结果整理返回识别文本、置信度main.py串联整个流程打印识别结果、可视化输出requirements.txt依赖列表一行一个包名为什么要这个结构因为OpenCV项目的调参是常态。把min_area、circularity_threshold、hsv_lower这些常数集中放进config.py调参时不用翻遍整个项目也方便写一个小脚本做网格搜索。我在自己的项目里还会把“保存中间图”的开关也放进config调试时打开、部署时关掉非常省事。使用文档在这个项目里不是摆设。跑通之前最好先按文档里的“运行示例”走一遍观察输出目录里生成了哪些中间图再对照文档里的参数解释去理解每一段代码的作用。这样做比直接改代码试错快得多。如果你拿到的源码包没有文档按上面这个结构自己补一份也行几百字就够后续交接能省一天时间。3. 用OpenCV定位印章颜色分割、轮廓查找与霍夫圆变换这一章是项目的核心。目标是在一张A4扫描件里输出印章的圆心和半径为后续展开和识别提供干净输入。我会先讲红色分割再讲轮廓筛选最后讲直线和边缘检测如何做辅助判断。每一步都给出实际跑过的代码和参数范围。3.1 先把红色区域从扫描件里分离出来印章是红色的第一直觉是在RGB空间里找红色像素。但RGB颜色空间里红色的三个通道会随亮度一起变化扫描件偏亮时(255,0,0)可能变成了(200,120,120)单纯比较R通道会把灰色内容误判成红色。所以业内标准做法是转换到HSV空间。HSV把颜色H、饱和度S、明度V拆开。红色的色相范围在0到10和160到180两段OpenCV里H的范围是0到180所以不能用一个区间包住。下面的函数把两段掩码做或运算再通过形态学闭开运算清理细碎噪点import cv2 import numpy as np def extract_red_mask(image): # 输入为BGR图像输出为二值掩码白色区域表示候选红色 hsv cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 红色在HSV中跨0度和180度两段 lower_red1 np.array([0, 100, 100]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([160, 100, 100]) upper_red2 np.array([180, 255, 255]) mask1 cv2.inRange(hsv, lower_red1, upper_red1) mask2 cv2.inRange(hsv, lower_red2, upper_red2) mask cv2.bitwise_or(mask1, mask2) # 先闭运算填充印章笔画内的空隙再开运算去掉孤立噪点 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) return mask逻辑说明cvtColor把BGR转到HSVinRange分别提取两段红色bitwise_or合并MORPH_CLOSE先膨胀后腐蚀能填补印章文字笔画之间的细缝MORPH_OPEN先腐蚀后膨胀能去掉背景里零散的红色噪点。参数说明里最容易翻车的是S和V的下限100。扫描打印的印章颜色一般比较实100够用如果是淡红印泥建议降到60。具体调法把这段代码封装后用cv2.createTrackbar做三个滑动条实时看掩码效果。这里有一个血泪经验不要在纯白背景下调阈值一定要拿真实扫描件调否则换一张图就失效。3.2 用findContours筛出圆形候选框拿到红色掩码后下一步是找轮廓。cv2.findContours会把所有白色连成一片的区域变成点集我们要从中筛掉非圆形的干扰物比如红头文件标题字、红色Logo、印章里的五角星它和章的外圈分离。def find_seal_contours(mask, min_area3000, max_area200000): # RETR_EXTERNAL只取最外层轮廓避免印章内圆环重复检测 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) seals [] for cnt in contours: area cv2.contourArea(cnt) if area min_area or area max_area: continue perimeter cv2.arcLength(cnt, True) if perimeter 0: continue # 圆形度4π×面积/周长²正圆为1矩形约0.785 circularity 4 * np.pi * area / (perimeter * perimeter) if circularity 0.5: continue # 用最小外接圆代替矩形框对圆形印章更精确 (x, y), radius cv2.minEnclosingCircle(cnt) seals.append({ center: (int(x), int(y)), radius: int(radius), area: area, circularity: circularity }) return seals逻辑说明RETR_EXTERNAL是只返回最外层轮廓的关键参数。印章本身由外圈、内圈和文字组成如果不加这个参数一个章会被检测出五六层嵌套圆后续没法判断哪个是目标。CHAIN_APPROX_SIMPLE压缩轮廓点减少内存占用。参数说明min_area3000对应约直径60px的小章max_area200000对应约直径500px的大章。300dpi扫描件里实际公章直径一般在35到45mm换算成像素就是2万到4万之间。circularity0.5的阈值能把矩形表格线、长条红章滤掉但也会漏掉形状不规则的印章降到0.4可提高召回率代价是误检变多。这里我一般不用霍夫圆变换。cv2.HoughCircles在印章检测里表现不稳定因为印章外圈是圆环而不是实心圆圆检测经常把外圈、内圈、字迹中间的空白分别当成三个圆参数调起来很玄学。轮廓法直接基于连通域几何物理含义清晰出问题也好排查。3.3 边缘检测与直线检测的辅助判断有的印章中心有红五角星有的只有文字。五角星轮廓呈明显直线段可以用cv2.Canny加cv2.HoughLinesP把它找出来用来做两个事一是辅助过滤误检的圆形区域二是为下一章的旋转校正提供角度基准。直线检测是OpenCV里的经典操作也被很多人单独拿来做过车牌、身份证边缘检测思路是互通的。def detect_star_lines(gray_roi): # 输入为灰度ROI返回五角星或印章内文字形成的直线段 edges cv2.Canny(gray_roi, 50, 150) lines cv2.HoughLinesP( edges, rho1, thetanp.pi / 180, threshold50, minLineLength30, maxLineGap5 ) return lines逻辑说明Canny先做边缘检测HoughLinesP在边缘图上找线段。rho1是距离分辨率thetanp.pi/180是角度分辨率逐度搜索threshold50是投票数越大得线段越少越可靠minLineLength30过滤短碎线防止把毛笔笔锋的弯曲笔画误判成直线maxLineGap5允许线段之间最多5像素断口拼接。参数说明Canny的两个阈值50和150控制边缘强弱如果印章边缘比较虚把高阈值降到100。minLineLength要在运行后根据实际效果微调五角星的边长在200px时30px的minLineLength刚好能留下主要边线。用直线段做旋转校正时有个陷阱印章里的字是环形排列垂直线和水平线很少但五角星的边线与印章边缘平行可以先算每条线段的角度取众数作为整体方向偏差。后面第4章会用到这个角度做反旋转。4. 把圆形印章“摊平”并识别文字极坐标展开与OCR这一章解决“怎么读印章里的字”的问题。纯圆形文字直接送OCR基本是乱码必须先把环状文字展开成横向文本再做识别。我会把展开、模板匹配、OCR三条路线都过一遍并给出每种方案的适用场景。4.1 环形文字为什么要先极坐标展开印章上的公司名称是沿圆环排列的直接交给OCR识别率极低。因为OCR模型基本都是按水平文本训练的弯曲字符会被当成噪声。解决办法是把圆形ROI展开成矩形让环形文字在水平方向排开。OpenCV从4.0开始提供cv2.warpPolar不再需要手写cartToPolar映射。在展开之前先要精确取到印章的圆心和半径。第3章轮廓检测返回的center和radius是浮点数取整后要再确认一下圆心是否落在印章中心。如果圆心偏差超过半径的5%展开后的文字会明显扭曲。这个偏差常见于印章边框和文字连通以后minEnclosingCircle被外边一个杂点撑大。解决办法是对掩码做一次腐蚀把外部杂点先去掉再重新拟合圆。def unwarp_seal(seal_img, center, radius): # 把圆形ROI展开为(2r, r)的矩形图 polar cv2.warpPolar( seal_img, (radius * 2, radius), center, radius, cv2.WARP_POLAR_LINEAR ) # 展开图默认竖向旋转90度让文字横向 unwrapped cv2.rotate(polar, cv2.ROTATE_90_COUNTERCLOCKWISE) return unwrapped逻辑说明warpPolar的输入是ROI图像最好先按第3章的半径裁剪出包含印章的方形区域输出宽为2r、高为r每一行对应原图中同一个半径上的像素。参数WARP_POLAR_LINEAR表示按线性方式采样保证展开后文字宽度相对于原图不失真。默认展开结果是竖着排的旋转90度后环形文字大致变成水平方便OCR按行切分。参数说明为什么输出宽取radius*2因为圆周长是2πr理论上一圈要2πr个像素才不丢信息但warpPolar为了保留上下半圆关系常用2r宽度展开后文字会被横向压缩。实际测试中把宽度提高到int(2 * np.pi * radius)效果更好代价是图变长、OCR耗时略增。这一步用默认的线性插值就够不需要更高阶。一个常见问题是展开后文字方向是反的因为印章上的字有的按顺时针排、有的按逆时针排。展开结果有两种方向可以都试一次交给后面OCR选置信度高的结果。4.2 用模板匹配先定位章中心的五角星印章中心通常有国徽或五角星它们是天然的方向锚点。即使不做展开也可以先用模板匹配确定印章是否正立为OCR结果排序提供参考。OpenCV的模板匹配是图像处理项目里最常见的特征定位手段适合在已知目标外观时使用。def locate_center_star(unwrapped, star_template): # 在展开图里找五角星/国徽的位置返回匹配度 gray cv2.cvtColor(unwrapped, cv2.COLOR_BGR2GRAY) res cv2.matchTemplate(gray, star_template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) return max_val, max_loc逻辑说明matchTemplate在unwrapped上滑动star_template计算每个位置的归一化相关系数TM_CCOEFF_NORMED对光照变化不敏感取值范围 -1 到 1大于0.6基本可以认为找到了。模板图自己截图制作即可尺寸不要超过印章半径的1/4。参数说明star_template必须和unwrapped的灰度范围一致最好都是从同一张扫描件里裁的模板如果你的印章中心是国徽而不是五角星模板替换就行不影响流程。匹配度会受展开图的扭曲影响所以这块只做辅助判断不作为硬性过滤条件。4.3 OCR识别的两种路线Tesseract与PaddleOCR展开图准备好了识别文字就有两条路。第一条是用Tesseract。轻量、离线、安装简单但中文识别效果一般尤其是仿宋体的印章字经常把“有限”识别成“目限”。第二条是用PaddleOCR识别中文效果明显好模型内部对方向有分类还能输出每个文本框的坐标。对于这个项目我更推荐PaddleOCR。from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def recognize_seal_text(unwrapped): result ocr.ocr(unwrapped, clsTrue) if not result or not result[0]: return [] lines [] for box, line in result[0]: text line[0] prob line[1] # box是四个角点计算左上角x和y用于排序 xs [p[0] for p in box] ys [p[1] for p in box] lines.append({ text: text, prob: prob, x: min(xs), y: min(ys) }) lines.sort(keylambda item: item[y]) return lines逻辑说明use_angle_clsTrue启用方向分类器能把倒置的文本先转正clsTrue在识别时再次启用方向分类。result[0]是单张图片的识别结果每个元素包含文本框坐标和文本、置信度。输出后按y坐标排序是为了把同一行文字连读。由于展开图里的环形文字被压成一行这里排序后还要按x顺序拼接。参数说明langch指定中文模型第一次运行会下载模型文件建议在网络稳定时执行。show_logFalse关闭调试日志不然控制台会被刷屏。如果跑出来的文字顺序是乱的不要急着去调OCR参数先检查展开图是否水平——问题多半出在圆心不准而不是模型。还有一个技巧对展开图做一次cv2.threshold二值化把灰度印章变成黑白能提高PaddleOCR的识别率但要注意二值化阈值用Otsu算法别写死。代码_, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)为什么这样有效因为盖章印泥不均匀灰度图上有大量中间色调二值化消除了这些噪声让文字边界更锐利。如果遇到笔画粘连严重的情况OpenCV contrib里有cv2.ximgproc.thinning可以做骨架细化但PaddleOCR对这类极端预处理反而敏感容易丢失上下文所以这个操作只在Tesseract模板切字场景用不建议整条链路都上。5. 避坑/常见问题排查让印章识别从“能跑”到“能信”有很多源码包能跑通但换一批扫描件就崩。问题不总在OCR而常常在掩码和轮廓环节。下面五条是我在多个OpenCV图像处理项目里反复踩过的坑按“现象→原因→解决”写可以直接对号入座。5.1 红色掩码在扫描件上大范围漏检现象同一套参数在屏幕截图上能准确框住印章换成扫描件后掩码变成了筛子印章内部大片空洞轮廓检测直接失败。原因扫描仪偏色严重暗红色印泥在HSV里饱和度不够100明度又偏高超出了预设阈值。解决不要指望一组HSV阈值通吃。把lower_red1的S从100降到50V从100降到50同时把两段H范围分别扩展到0-15和155-180。如果还漏就先做cv2.convertScaleAbs增加对比度或者用cv2.xphoto.createSimpleWB()做白平衡。我在config.py里加了red_s_min和red_v_min两个参数批量处理时先用一个样张扫描文件统计直方图再微调。5.2 一个印章被检测成三四个候选框现象find_seal_contours返回了好几个同心圆坐标可视化时画了两三个重叠的圆圈。原因印章由外圈、内圈、中心五角星组成虽然用了RETR_EXTERNAL但如果外圈笔画断裂连通域就会裂成多块每块都满足圆形度。解决先对掩码做强闭运算把kernel从5x5放大到15x15。如果断裂严重改用cv2.dilate膨胀两次让外圈重新连成一个整体。另一个手段是在轮廓筛选时增加“最大外接圆半径”判断多个候选圆如果圆心距离小于10px只保留面积最大的那个。5.3 warpPolar展开后的文字是反的还是倒的现象展开图里的文字从左往右读没错但整体上下颠倒或左右镜像。原因印章文字方向有两种顺时针排布和逆时针排布warpPolar默认从0度角开始采样不保证把文字摆正。解决不要把展开图直接送OCR。识别前对同一个ROI做四次尝试原图、旋转90度、旋转180度、旋转270度各展开一次取OCR平均置信度最高的一组。这个操作听起来耗时但印章ROI很小四次展开和识别也就多几百毫秒。我在课程设计里就是用这个办法把识别率从60%提到了85%。5.4 PaddleOCR在Python 3.12上装不上现象pip install paddlepaddle成功但import paddle报ModuleNotFoundError或OSError。原因PaddlePaddle的CPU wheel包发布滞后新版Python没有匹配版本pip会把旧版本强行装上导致运行期C库崩溃。解决项目里固定用Python 3.10创建虚拟环境时用python3.10 -m venv sealenv。先装CPU版pip install paddlepaddle2.6.0再装paddleocr最后验证python -c import paddle; print(paddle.__version__)。不要用conda默认的base环境跑。5.5 OCR结果乱序公司名称被拆成两个单词现象识别出一堆文本但拼接后顺序完全不对比如“有限公司”跑到了“XX建设”前面。原因展开图里的文字虽然横向排布但同一个词的首尾可能被切到了矩形图的两个边缘OCR内部按文本框聚类把首尾切成了两块。解决不要完全信任OCR的文本框顺序。把返回的文本框按角点排序找到y坐标最小的那一行再按x坐标从左到右拼接。如果首尾被切就用一个简单规则把展开图再旋转180度识别一次取两次识别结果字符串中重叠最多的那个。这个后处理逻辑要写在seal_ocr.py里而不是临时在main.py里打补丁。6. 进阶把单张截图识别升级成批量扫描件识别单张跑通只是第一步实际落地要处理一个目录里的上百份扫描件。把seal_detect.py和seal_ocr.py串成一个批量脚本输出CSV报告再加一个简单的参数网格搜索就能在验证集上自动找最优阈值。import os import csv import cv2 from seal_detect import extract_red_mask, find_seal_contours from seal_ocr import recognize_seal_text, unwarp_seal def batch_predict(img_dir, output_csv): rows [] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .png)): continue img cv2.imread(os.path.join(img_dir, name)) mask extract_red_mask(img) seals find_seal_contours(mask) if not seals: rows.append([name, , ]) continue best max(seals, keylambda s: s[area]) unwrapped unwarp_seal(img, best[center], best[radius]) lines recognize_seal_text(unwrapped) text .join([line[text] for line in lines]) rows.append([name, text, fcenter{best[center]}, r{best[radius]}]) with open(output_csv, w, newline, encodingutf-8) as f: csv.writer(f).writerows(rows)注意best取面积最大轮廓的方案只适配单章扫描件。一张合同上可能同时有单位公章和发票专用章那就不要取最大而是遍历所有seals分别识别再按客户名称分列。验证方法更关键每跑一张图都把mask.png、unwrapped.png和result.txt写到debug目录肉眼看完中间产物再动手改代码。如果掩码坏了不要浪费时间调OCR一定是前面定位环节的问题。我刚做这个项目时习惯直接打印识别文本错了就改OCR参数最后发现都是掩码惹的祸。后来养成的习惯是先看掩码对不对、再看展开图平不平、最后才看OCR结果。这个习惯帮我省下大量翻车时间。希望帮到你。本文还有配套的精品资源点击获取