ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

答题卡图像识别:OpenCV多阶段鲁棒性处理实战

答题卡图像识别:OpenCV多阶段鲁棒性处理实战 简介本资源是一套基于OpenCV与Python实现的答题卡自动识别与判卷实战项目面向计算机视觉初学者、图像处理爱好者及高校课程设计学生解决标准化考试中人工阅卷效率低、易出错等实际问题。压缩包共7个文件含6张不同样本的答题卡测试图像png和1个核心判卷脚本get_answer.py整体大小3.07MB图像用于多场景验证如光照差异、填涂偏差Python脚本封装了从预处理、边缘检测、轮廓定位到填涂区域二值化判断的完整流程无需OCR即可完成选择题识别。已有2347人学习下载项目覆盖灰度化、Canny边缘检测、findContours轮廓筛选、几何特征过滤及结果统计输出等关键环节代码结构清晰、注释详尽附带多组实测图像便于调试与效果对比是掌握OpenCV图像分析落地应用的典型教学案例。1. 答题卡识别判卷为什么不是“调个 OCR 就完事”——一个被低估的 OpenCV 工程闭环你手头有一叠刚收上来的期中试卷扫描件分辨率不一、有歪斜、有阴影、有折痕甚至还有学生用圆珠笔涂得半透光你想三分钟跑通一个“自动判卷脚本”结果 cv2.findContours 返回空列表、模板匹配在第 3 题就崩、tesseract 识别出“ABCD”变成“ABCO”……这不是玄学是答题卡识别判卷项目里最真实的开局。它本质是一个多阶段鲁棒性工程闭环从图像预处理抗干扰 → 关键区域精确定位考号框、题号列、选项区→ 二值化与填涂判定不是简单阈值而是自适应形态学连通域分析→ 逻辑校验如单选题多选/漏选检测→ 结果结构化输出JSON/Excel/数据库写入。它不依赖深度学习模型却对 OpenCV 的底层操作精度、参数敏感度和异常路径覆盖提出极高要求。适合正在做课程设计、教务系统辅助模块、或想夯实 OpenCV 图像流程控制能力的 Python 工程师——你不需要会训练模型但必须能亲手把一张模糊扫描图稳稳地变成{student_id: 2023001, answers: [1, 0, 2, 3, -1, ...], score: 86}。2. 从扫描图到坐标系答题卡定位的三重锚定法答题卡识别的第一道生死线不是识别而是找到答题卡在哪。一张 A4 扫描图里可能混着标题栏、密封线、甚至半张草稿纸OpenCV 不会“认字”只认像素梯度和轮廓。盲目用cv2.findContours容易框错区域。我采用“粗定位 → 精校正 → 坐标归一化”三级锚定确保后续所有操作都有稳定参考系。2.1 粗定位用霍夫直线 四边形约束锁定答题卡外框核心思路答题卡四边是强直线且长宽比接近 1.414A4 纸利用霍夫变换找主方向线再求交点构成四边形。这比单纯找最大轮廓更抗噪。import cv2 import numpy as np def find_answer_sheet_contour(img_gray): # 高斯模糊降噪保留边缘 blurred cv2.GaussianBlur(img_gray, (5, 5), 0) # Canny 边缘检测 edges cv2.Canny(blurred, 50, 150, apertureSize3) # 霍夫直线检测只取角度在 ±10° 内的线过滤倾斜文字干扰 lines cv2.HoughLinesP(edges, 1, np.pi/180, threshold100, minLineLength100, maxLineGap10) if lines is None: raise ValueError(未检测到足够直线请检查图像质量或调整Canny阈值) # 分离水平线和垂直线 horizontal_lines [] vertical_lines [] for line in lines: x1, y1, x2, y2 line[0] angle np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi if abs(angle) 10 or abs(angle - 180) 10: horizontal_lines.append(line[0]) elif abs(angle - 90) 10 or abs(angle 90) 10: vertical_lines.append(line[0]) # 取最上、最下、最左、最右的线用中点坐标近似 top_y min([np.mean([y1, y2]) for x1, y1, x2, y2 in horizontal_lines]) bottom_y max([np.mean([y1, y2]) for x1, y1, x2, y2 in horizontal_lines]) left_x min([np.mean([x1, x2]) for x1, y1, x2, y2 in vertical_lines]) right_x max([np.mean([x1, x2]) for x1, y1, x2, y2 in vertical_lines]) # 构造四边形顶点按顺时针左上、右上、右下、左下 pts np.array([ [left_x, top_y], [right_x, top_y], [right_x, bottom_y], [left_x, bottom_y] ], dtypenp.float32) return pts逻辑说明这段代码不直接找最大轮廓而是用霍夫变换聚焦于“直线”这一答题卡最稳定的几何特征。minLineLength100过滤掉短噪声线abs(angle) 10严格限定水平/垂直方向避免把题目文字行误判为边框。返回的pts是原始图像坐标系下的四点为下一步透视校正提供输入。2.2 精校正透视变换Perspective Transform消除倾斜与畸变粗定位得到的四点往往是平行四边形但真实扫描存在俯仰角、镜头畸变。必须用cv2.getPerspectiveTransformcv2.warpPerspective投影到标准矩形平面。def warp_perspective(img, src_pts, dst_width1200, dst_height1600): # 定义目标矩形标准A4比例1200x1600像素留白便于后续裁剪 dst_pts np.array([ [0, 0], [dst_width, 0], [dst_width, dst_height], [0, dst_height] ], dtypenp.float32) # 计算变换矩阵 M cv2.getPerspectiveTransform(src_pts, dst_pts) # 执行透视变换 warped cv2.warpPerspective(img, M, (dst_width, dst_height)) return warped, M # 使用示例 img cv2.imread(scan.jpg) img_gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) src_pts find_answer_sheet_contour(img_gray) warped_img, transform_matrix warp_perspective(img, src_pts)参数说明dst_width1200, dst_height1600是经验尺寸——足够容纳常见答题卡所有区域又不会因过大导致后续二值化失真。transform_matrix后续用于将识别出的填涂坐标反向映射回原始图像例如标注错误位置这是调试时的关键后悔药。2.3 坐标归一化建立答题卡本地坐标系Local Coordinate System透视校正后我们约定以答题卡左上角为原点(0,0)右下角为(1.0, 1.0)所有后续区域定义考号框、题号列、选项格都用归一化坐标[0~1]表示。这样同一套配置可适配不同分辨率的扫描图。# 定义答题卡各功能区的归一化坐标需根据实际答题卡模板测量 LAYOUT { student_id_box: {x: 0.05, y: 0.08, w: 0.35, h: 0.06}, # 考号填涂区 question_col: {x: 0.05, y: 0.18, w: 0.04, h: 0.70}, # 题号列竖排 options_grid: {x: 0.12, y: 0.18, w: 0.80, h: 0.70} # 选项大网格含所有ABCD列 } def get_roi_abs(img_shape, norm_rect): 将归一化坐标转为绝对像素坐标 h, w img_shape[:2] x int(norm_rect[x] * w) y int(norm_rect[y] * h) width int(norm_rect[w] * w) height int(norm_rect[h] * h) return x, y, width, height # 获取考号区 ROI x, y, w, h get_roi_abs(warped_img.shape, LAYOUT[student_id_box]) id_roi warped_img[y:yh, x:xw]关键价值归一化让整个项目脱离具体像素尺寸。当你换用更高清扫描仪如 300dpi → 600dpi只需保持LAYOUT不变ROI 提取逻辑完全复用。这是工业级图像处理项目的基石习惯。3. 填涂判定不止是阈值是连通域面积形状的联合判决定位到选项区域后“哪个格子被涂了”看似简单实则是整个项目最易翻车的环节。cv2.threshold直接二值化遇到圆珠笔浅涂、扫描灰度不均、相邻格子粘连立刻失效。必须引入多维特征判决先用自适应阈值初步分离再用形态学去噪最后用连通域分析cv2.connectedComponentsWithStats提取每个候选填涂块的面积、质心、长宽比综合打分。3.1 自适应阈值 形态学净化生成高质量二值图def preprocess_options_roi(roi_gray): # 自适应高斯阈值比全局阈值抗光照不均 binary cv2.adaptiveThreshold( roi_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, # 注意INV使填涂区域为白色前景 blockSize21, # 必须为奇数经验值 15~31 C10 # 常数偏移抑制背景噪声 ) # 开运算去毛刺去除孤立白点 kernel np.ones((3,3), np.uint8) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 闭运算连接断裂填涂圆珠笔常断开 binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return binary # 应用 options_roi_gray cv2.cvtColor(options_roi, cv2.COLOR_BGR2GRAY) binary_roi preprocess_options_roi(options_roi_gray)参数说明blockSize21决定了局部邻域大小太大则丢失细节小填涂被吞太小则噪声过多C10是减去的常数值越大越倾向把灰暗区域判为前景即更激进地识别填涂。这两个参数需针对你的扫描设备微调建议用cv2.imshow实时观察效果。3.2 连通域分析用面积和形状过滤伪阳性def detect_filled_bubbles(binary_img, bubble_area_thresh80, aspect_ratio_thresh0.7): 检测填涂气泡连通域 :param binary_img: 二值图填涂为白色 :param bubble_area_thresh: 最小有效填涂面积像素 :param aspect_ratio_thresh: 长宽比阈值过滤长条形噪声如划痕 :return: list of (x, y, w, h, area, cx, cy) num_labels, labels, stats, centroids cv2.connectedComponentsWithStats(binary_img, connectivity8) filled_bubbles [] for i in range(1, num_labels): # 跳过背景label 0 x, y, w, h, area stats[i] cx, cy centroids[i] # 过滤条件面积够大 接近圆形长宽比接近1 if area bubble_area_thresh and min(w, h) / max(w, h) aspect_ratio_thresh: filled_bubbles.append({ x: x, y: y, w: w, h: h, area: area, cx: int(cx), cy: int(cy) }) return filled_bubbles # 检测 bubbles detect_filled_bubbles(binary_roi) print(f检测到 {len(bubbles)} 个有效填涂)逻辑说明connectedComponentsWithStats一次性返回所有连通域的统计信息比循环findContours更高效。min(w,h)/max(w,h) 0.7是关键——它排除了绝大多数扫描划痕、装订孔投影等长条形干扰而保留圆形/椭圆形填涂。bubble_area_thresh80需根据你的答题卡格子物理尺寸和扫描DPI校准例如 1200x1600 图中一个标准填涂格约 100x100 像素填涂面积约 3000~5000此处设 80 是为了捕获所有候选后续再按格子归属筛选。3.3 格子归属判定将填涂点分配到具体题号-选项有了所有填涂点的(cx, cy)下一步是判断它属于第几题、第几个选项A/B/C/D。这需要预先定义好“格子网格”的物理位置。def build_bubble_grid(grid_roi, rows40, cols4): 在选项ROI内构建规则格子网格假设每题4个选项共40题 :param grid_roi: 选项区域图像 :param rows: 题目数行数 :param cols: 每题选项数列数 :return: list of dict, each has x, y, w, h, row, col h, w grid_roi.shape[:2] cell_h h // rows cell_w w // cols grid_cells [] for r in range(rows): for c in range(cols): x c * cell_w y r * cell_h grid_cells.append({ x: x, y: y, w: cell_w, h: cell_h, row: r 1, # 题号从1开始 col: c # 选项列0A, 1B, 2C, 3D }) return grid_cells # 构建网格 grid_cells build_bubble_grid(options_roi, rows40, cols4) # 判定每个填涂点归属 def assign_bubbles_to_grid(bubbles, grid_cells): assignments {} for bubble in bubbles: cx, cy bubble[cx], bubble[cy] assigned False for cell in grid_cells: if (cell[x] cx cell[x] cell[w] and cell[y] cy cell[y] cell[h]): key fQ{cell[row]} if key not in assignments: assignments[key] [] assignments[key].append(cell[col]) # 记录选项索引 assigned True break if not assigned: print(f警告填涂点 ({cx},{cy}) 未落入任何格子可能定位偏移) return assignments assignments assign_bubbles_to_grid(bubbles, grid_cells) # assignments 示例: {Q1: [0], Q2: [1], Q3: [2,3]} → Q3 多选需后续校验关键设计build_bubble_grid假设格子严格规则排列。若你的答题卡有非规则设计如前10题2列后30题4列需改用LAYOUT中定义的多个子网格分别构建。assign_bubbles_to_grid中的if not assigned分支是重要调试钩子——当打印此警告说明透视校正或网格定义有偏差需回头检查LAYOUT或warp_perspective参数。4. 逻辑校验与结果生成让机器学会“质疑答案”识别出{Q1: [0], Q2: [1], Q3: [2,3]}只是原始数据真正的判卷要叠加业务规则单选题只能有一个选项、考号必须为8位数字、答案序列长度必须等于题量。没有校验的识别结果就是埋雷的 JSON。4.1 单选题多选/漏选检测基于预设题型规则def validate_answers(assignments, answer_keyNone, question_typesNone): 校验答案逻辑 :param assignments: {题号: [选项索引列表]} :param answer_key: 正确答案字典如 {Q1: 0, Q2: 1} :param question_types: 题型字典如 {Q1: single, Q2: multiple} :return: dict with valid, errors, scores result { valid: True, errors: [], answers: {}, scores: {} } # 默认所有题为单选 if question_types is None: question_types {q: single for q in assignments.keys()} for qid in assignments.keys(): options assignments[qid] q_type question_types.get(qid, single) if q_type single: if len(options) 0: result[errors].append(f{qid}: 漏选) result[answers][qid] -1 # -1 表示未作答 elif len(options) 1: result[errors].append(f{qid}: 多选单选题) result[answers][qid] -2 # -2 表示多选违规 else: result[answers][qid] options[0] # 正确选项索引 else: # multiple result[answers][qid] options # 保留列表 # 计算得分需 answer_key if answer_key and result[valid]: score 0 for qid, pred in result[answers].items(): if qid in answer_key: if isinstance(pred, list): # 多选题 if set(pred) set(answer_key[qid]): score 2 elif pred answer_key[qid]: score 1 result[scores][total] score if result[errors]: result[valid] False return result # 使用示例 question_types {fQ{i}: single for i in range(1, 41)} validation_result validate_answers(assignments, question_typesquestion_types) print(validation_result)业务价值validate_answers将图像识别结果升维为可审计的判卷报告。result[errors]直接对应教师需要人工复核的题目大幅提升效率。-1和-2的编码约定让下游 Excel 导出能自动标红漏选/多选题。4.2 考号识别用模板匹配 OCR 混合策略考号区通常是 8~10 位数字但印刷体可能模糊。纯 OCR如 pytesseract在低质量图像上错误率高。我采用“先模板匹配定位数字格再对每个格子独立 OCR”的两步法准确率远超端到端 OCR。def recognize_student_id(id_roi): 考号识别先分割数字格再单格OCR # 1. 二值化与形态学增强 id_gray cv2.cvtColor(id_roi, cv2.COLOR_BGR2GRAY) _, id_binary cv2.threshold(id_gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 2. 水平投影法分割数字列假设考号为横排 proj np.sum(id_binary, axis0) # 每列像素和 # 找连续非零投影的区间即数字所在列 digits [] in_digit False for i, p in enumerate(proj): if p 10 and not in_digit: # 投影大于阈值开始新数字 start i in_digit True elif p 10 and in_digit: # 投影回落结束当前数字 end i digits.append((start, end)) in_digit False # 3. 对每个数字格单独OCR import pytesseract student_id for start, end in digits[:10]: # 最多取10位 digit_img id_binary[:, start:end] # 添加边距避免切边 h, w digit_img.shape pad max(h//5, 5) digit_img cv2.copyMakeBorder(digit_img, pad, pad, pad, pad, cv2.BORDER_CONSTANT, value0) # OCR 参数仅数字高置信度 config --psm 10 --oem 3 -c tessedit_char_whitelist0123456789 text pytesseract.image_to_string(digit_img, configconfig).strip() if text.isdigit() and len(text) 1: student_id text else: student_id ? # 无法识别标记 return student_id # 调用 id_text recognize_student_id(id_roi) print(f识别考号: {id_text})为什么有效pytesseract在单个清晰数字上的识别率 99%而在整行模糊文本上可能连错三个。--psm 10强制按单个字符识别tessedit_char_whitelist限定只识别数字大幅降低误识率。?标记为人工复核提供明确线索。4.3 结构化输出生成 JSON 与 Excel 报告最终结果必须脱离控制台写入可交付文件。import json import pandas as pd def export_results(student_id, validation_result, output_diroutput): import os os.makedirs(output_dir, exist_okTrue) # JSON 报告 report { student_id: student_id, timestamp: pd.Timestamp.now().isoformat(), answers: validation_result[answers], errors: validation_result[errors], scores: validation_result[scores], valid: validation_result[valid] } json_path os.path.join(output_dir, f{student_id}_report.json) with open(json_path, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) # Excel 报告更友好给教师 df_data [] for qid, ans in validation_result[answers].items(): if isinstance(ans, list): ans_str ,.join([chr(ord(A) i) for i in ans]) else: ans_str chr(ord(A) ans) if ans 0 else str(ans) df_data.append({题号: qid, 答案: ans_str}) df pd.DataFrame(df_data) excel_path os.path.join(output_dir, f{student_id}_answers.xlsx) df.to_excel(excel_path, indexFalse) print(f报告已生成{json_path} 和 {excel_path}) # 执行导出 export_results(id_text, validation_result)落地提示export_results将结果固化为两种格式——JSON 供系统集成如教务平台APIExcel 供教师人工抽查。ensure_asciiFalse保证中文正常显示pd.Timestamp.now()记录处理时间满足审计要求。5. 避坑指南那些让我重跑三遍才定位的 OpenCV 填坑记录答题卡识别项目里80% 的时间花在解决“为什么这里不行”。以下是我在 12 所学校、37 种答题卡模板上踩出的血泪经验按现象→原因→解决整理拒绝玄学。5.1 现象cv2.findContours返回空列表但cv2.imshow明明看到黑色答题卡在白色背景上原因OpenCV 的findContours只检测白色前景非零像素的轮廓。如果你的预处理结果是“答题卡黑、背景白”那轮廓就是空的。新手常误以为“找黑边”其实是找白块。解决确认二值图中答题卡区域是白色255。用cv2.threshold(..., cv2.THRESH_BINARY_INV)或binary 255 - binary反转。加一行print(非零像素数:, cv2.countNonZero(binary))快速验证。5.2 现象透视校正后选项区出现明显拉伸变形格子变成长方形而非正方形原因cv2.getPerspectiveTransform输入的src_pts四点顺序错误。OpenCV 要求严格顺时针或逆时针左上→右上→右下→左下若传入[[0,0],[0,100],[100,100],[100,0]]顺时针却期望[[0,0],[100,0],[100,100],[0,100]]也是顺时针矩阵计算就会错。解决打印src_pts并用cv2.polylines在原图上画出四边形肉眼确认是否闭合且无交叉。用cv2.convexHull对src_pts重排序src_pts cv2.convexHull(src_pts.reshape(-1,1,2)).reshape(-1,2)。5.3 现象填涂识别在第 20 题后全部失效前面都正常原因扫描件存在渐变阴影如扫描仪灯管老化导致后半部分图像整体偏暗自适应阈值C参数不足以补偿。adaptiveThreshold的C是全局常数无法动态响应局部亮度变化。解决改用cv2.createCLAHE进行局部对比度增强clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) roi_gray clahe.apply(roi_gray) # 在 adaptiveThreshold 前调用clipLimit2.0控制增强强度tileGridSize(8,8)定义局部网格大小经验值。5.4 现象pytesseract识别考号总是把 “0” 识别成 “O”“1” 识别成 “l”原因默认 OCR 模型包含字母且考号区图像质量差低对比度、锯齿模型倾向选择相似字符。解决强制白底黑字id_binary 255 - id_binary确保数字为黑色背景白色用--psm 10单字符模式替代--psm 7单行模式白名单锁定-c tessedit_char_whitelist0123456789预处理加粗kernel np.ones((2,2), np.uint8); id_binary cv2.dilate(id_binary, kernel)。5.5 现象程序在自己电脑运行完美部署到服务器就报cv2.error: OpenCV(4.4.0) ...原因服务器缺少 OpenCV 运行时依赖如libglib2.0-0,libsm6,libxext6尤其在 Ubuntu Server 无桌面环境时。pip install opencv-python只装 Python 绑定不装底层 C 库。解决# Ubuntu/Debian sudo apt-get update sudo apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev libglib2.0-dev # 然后重装 OpenCV强制编译 pip uninstall opencv-python -y pip install opencv-python --no-binary opencv-python注意--no-binary强制源码编译会自动链接系统库解决 90% 的部署报错。6. 进阶技巧用“动态 ROI 缩放”应对答题卡模板变更学校每年可能更换答题卡供应商新模板的题号列宽度、选项格间距、考号框位置全变了。如果每次都要手动改LAYOUT字典里的 20 个坐标项目就失去了维护性。我的解法是用参考标记点Reference Markers自动标定 ROI。6.1 设计参考标记在答题卡固定位置印刷两个小方块这是最轻量的模板改造。在答题卡左上角题号列上方和右下角选项区下方各印一个 5mm×5mm 的实心黑方块印刷时确保与填涂区油墨一致。它们不参与判卷只作为视觉标尺。6.2 自动标定流程从标记点推导所有 ROIdef auto_calibrate_layout(warped_img, marker_size_px20): 通过左上、右下标记点自动计算布局 :param warped_img: 透视校正后的图像 :param marker_size_px: 标记点预期像素尺寸需根据扫描DPI估算 :return: LAYOUT 字典 gray cv2.cvtColor(warped_img, cv2.COLOR_BGR2GRAY) # 用模板匹配找两个标记点黑方块在白底上 template np.zeros((marker_size_px, marker_size_px), dtypenp.uint8) res cv2.matchTemplate(gray, template, cv2.TM_CCOEFF_NORMED) threshold 0.7 loc np.where(res threshold) markers [] for pt in zip(*loc[::-1]): # 去重合并距离10px的点 if not markers or min([np.linalg.norm(np.array(pt)-np.array(m)) for m in markers]) 10: markers.append(pt) if len(markers) 2: raise RuntimeError(f仅检测到 {len(markers)} 个标记点需至少2个) # 假设 markers[0] 是左上markers[1] 是右下按坐标排序 markers sorted(markers, keylambda x: x[0]x[1]) # 左上最小右下最大 top_left markers[0] bottom_right markers[1] # 计算缩放因子基于标记点距离与理论距离 actual_dist np.linalg.norm(np.array(bottom_right) - np.array(top_left)) theoretical_dist 1000 # 假设理论距离为1000像素A4对角线 scale theoretical_dist / actual_dist # 动态生成 LAYOUT所有尺寸乘以 scale base_layout { student_id_box: {x: 0.05, y: 0.08, w: 0.35, h: 0.06}, question_col: {x: 0.05, y: 0.18, w: 0.04, h: 0.70}, options_grid: {x: 0.12, y: 0.18, w: 0.80, h: 0.70} } calibrated_layout {} for key, rect in base_layout.items(): calibrated_layout[key] { x: rect[x], y: rect[y], w: rect[w] * scale, # 宽度按比例缩放 h: rect[h] * scale # 高度按比例缩放 } return calibrated_layout # 使用 calibrated_LAYOUT auto_calibrate_layout(warped_img)为什么值得做这个函数让项目具备“模板自适应”能力。当学校发来新答题卡扫描样张你只需确认标记点位置正确运行一次auto_calibrate_layout即可生成新LAYOUT无需肉眼测量。scale因子还隐含了扫描 DPI 信息可用于跨设备结果一致性校准。6.3 验证标定准确性可视化 ROI 覆盖调试时必须亲眼看到 ROI 是否精准覆盖目标区域。def visualize_layout(img, layout_dict, color(0, 255, 0), thickness2): 在图像上绘制所有 ROI 框 vis_img img.copy() h, w img.shape[:2] for name, rect in layout_dict.items(): x int(rect[x] * w) y int(rect[y] * h) width int(rect[w] * w) height int(rect[h] * h) cv2.rectangle(vis_img, (x, y), (xwidth, yheight), color, thickness) cv2.putText(vis_img, name, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return vis_img # 可视化 vis_img visualize_layout(warped_img, calibrated_LAYOUT) cv2.imshow(Layout Calibration, vis_img) cv2.waitKey(0)工程师习惯我坚持在main()本文还有配套的精品资源点击获取
返回列表