opencv 轮廓判别法(find contour)使用详解

本文介绍了使用OpenCV的findContours和drawContours函数进行手写数字识别的过程,强调了二值图像必须为黑底白字以及findContours函数会改变原图像的重要性。此外,探讨了识别过程中存在的问题,包括图像预处理、边缘检测和干扰去除,并提出了可能的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本周学习了手写数字识别

////////////////////////////////////////////////////////////////////////////////////////////////////

我先尝试了第一种方法:基于轮廓查找的识别方法

这种方法主要运用了两个函数:findcontourdrawcontour

其中,第一个函数可以实现对于数字图像的轮廓查找,将查找到的点通过点向量的形式保存在了contour(为findcontour的第二个函数参数)中,然后通过drawcontour将查找到的轮廓描出来!

NOTE:需要注意的是,这两个函数是共同使用的,即有find则有draw。最重要的一点是他们共同使用了contour中的数据,换句话说,findcontour查找到了二值图像中的点向量信息(必须是二值图像,而且还必须是黑底白字貌似才能查找),然后在drawcontour函数中,就回会得到contour的数据。

好处在于,灰度图得到的数据,可以在原彩色图中被利用,从而实现了彩色图像数据的检测。

如图所示:

 

 

通过上图可以看出,我们获得了contour是(函数的第二个参数)后,将org彩色图给了lunkuo,在原图上就可以检测出数字。

NOTE:这里有一个问题一定要注意!!!!

(1)函数findcontours从二值图像中提取轮廓,并且返回提取轮廓的数目。指针First_contour的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为NULL,则没有检测到轮廓(比如图像全部为黑)。

 

(2)在做阈值处理时,一定要注意最终二值图像必须为黑底白字,如果是白底黑字,那么我们就需要用:

 

如果此处我们改为白底黑字,即CV_THRESH_BINARY,则会出现了误检情况,如图所示,最外层红色大框为误检测!!

 

而正常情况下黑底白字,能够正常识别,这点需要注意!

 

 

3注意:findContours()运行的时候,这个图像会被直接涂改,因此如果是将来还有用的图像,应该复制之后再传给findContours()

import numpy as np import cv2 import os # 配置参数 CONFIG = { 'template_size': (57, 88), 'binary_thresh': 10, 'min_contour_width': 40, 'min_contour_height': 10, 'kernel_size': (9, 9), 'match_threshold': 0.35 # 匹配置信度阈值 } # 方法1: 使用绝对路径 template_image_path =r"opencv_实例\digital recognition\template.png" # 模板图像 input_image_path = r"opencv_实例\digital recognition\test.png" # 待识别图像 # 方法2: 使用相对路径(推荐) # template_image_path = "test.png" # 模板图像 # input_image_path = "template.png" # 待识别图像 def cv_show(name, img): """显示图像的辅助函数""" cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() def list_directory_contents(): """列出当前目录和可能的图像文件""" current_dir = os.getcwd() print(f"\n当前工作目录: {current_dir}") # 列出当前目录的文件 try: files = os.listdir(current_dir) image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'] image_files = [f for f in files if any(f.lower().endswith(ext) for ext in image_extensions)] if image_files: print("\n当前目录中的图像文件:") for img_file in image_files: print(f" - {img_file}") else: print("\n当前目录中没有找到图像文件") # 检查是否存在数字识别文件夹 digit_recognition_dirs = [d for d in files if '数字识别' in d or 'digital' in d.lower()] if digit_recognition_dirs: print(f"\n找到相关目录: {digit_recognition_dirs}") except Exception as e: print(f"列出目录内容时出错: {e}") def extract_digit_templates(img): """从模板图像中提取数字模板""" # 转换为灰度图 ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ref = cv2.threshold(ref, CONFIG['binary_thresh'], 255, cv2.THRESH_BINARY_INV)[1] # 查找轮廓 refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(refCnts) == 0: print("警告:未在模板图像中找到任何轮廓") return {} # 绘制轮廓用于调试 debug_img = img.copy() cv2.drawContours(debug_img, refCnts, -1, (0, 0, 255), 3) # cv_show('template_contours', debug_img) # 按左到右排序轮廓 refCnts = sorted(refCnts, key=lambda c: cv2.boundingRect(c)[0]) digits = {} # 遍历每个轮廓提取数字模板 for (i, c) in enumerate(refCnts): (x, y, w, h) = cv2.boundingRect(c) roi = ref[y:y + h, x:x + w] roi = cv2.resize(roi, CONFIG['template_size']) digits[i] = roi print(f"提取模板数字 {i}: 原始尺寸({w}x{h}) -> 标准尺寸{CONFIG['template_size']}") print(f"成功提取 {len(digits)} 个数字模板") return digits def preprocess_input_image(image): """预处理输入图像""" # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 二值化 tophat = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV)[1] # Sobel边缘检测 gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) gradX = np.absolute(gradX) (minVal, maxVal) = (np.min(gradX), np.max(gradX)) if maxVal > minVal: # 避免除零错误 gradX = (255 * ((gradX - minVal) / (maxVal - minVal))).astype("uint8") else: gradX = gradX.astype("uint8") # 形态学操作 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, CONFIG['kernel_size']) sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, CONFIG['kernel_size']) gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) thresh = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, sqKernel) return tophat, thresh def find_digit_groups(thresh, image): """查找数字组的位置""" # 查找轮廓 threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(threshCnts) == 0: print("警告:未找到任何轮廓") return [] # 绘制所有轮廓用于调试 debug_img = image.copy() cv2.drawContours(debug_img, threshCnts, -1, (0, 0, 255), 3) # cv_show('all_contours', debug_img) locs = [] # 筛选符合条件的轮廓 for (i, c) in enumerate(threshCnts): (x, y, w, h) = cv2.boundingRect(c) if w > CONFIG['min_contour_width'] and h > CONFIG['min_contour_height']: locs.append((x, y, w, h)) print(f"找到符合条件的区域 {len(locs)}: 位置({x},{y}), 尺寸({w}x{h})") # 按左到右排序 locs = sorted(locs, key=lambda x: x[0]) print(f"共找到 {len(locs)} 个符合条件的数字组区域") return locs def count_holes(roi): """统计二值图像中的“洞”数""" contours, hierarchy = cv2.findContours(roi, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) hole_count = 0 if hierarchy is not None: for i in range(len(contours)): # hierarchy[0][i][3] == -1 表示外轮廓,!= -1 表示洞 if hierarchy[0][i][3] != -1: hole_count += 1 return hole_count def recognize_digits_in_group(group_roi, digits): """识别单个组中的所有数字,增加洞数辅助判别6和8""" # 预处理组图像 group_binary = cv2.threshold(group_roi, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 查找数字轮廓 digitCnts, hierarchy = cv2.findContours(group_binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(digitCnts) == 0: print("警告:在数字组中未找到轮廓") return [] # 按左到右排序 digitCnts = sorted(digitCnts, key=lambda c: cv2.boundingRect(c)[0]) group_output = [] for c in digitCnts: (x, y, w, h) = cv2.boundingRect(c) roi = group_binary[y:y + h, x:x + w] roi_resized = cv2.resize(roi, CONFIG['template_size']) # 用于模板匹配 # 计算与所有模板的匹配分数 scores = [] for (digit, digitROI) in digits.items(): result = cv2.matchTemplate(roi_resized, digitROI, cv2.TM_CCOEFF_NORMED) (_, score, _, _) = cv2.minMaxLoc(result) scores.append(score) # 找到最佳匹配 if scores: best_match_idx = int(np.argmax(scores)) best_score = scores[best_match_idx] # 洞数辅助判别6和8 if best_match_idx in [6, 8]: roi_for_hole = roi.copy() if roi_for_hole.shape[0] > 0 and roi_for_hole.shape[1] > 0: holes = count_holes(roi_for_hole) if best_match_idx == 8 and holes < 2: best_match_idx = 6 print(f"洞数辅助修正: 8→6 (洞数={holes})") elif best_match_idx == 6 and holes > 1: best_match_idx = 8 print(f"洞数辅助修正: 6→8 (洞数={holes})") if best_score > CONFIG['match_threshold']: group_output.append(str(best_match_idx)) print(f"识别数字: {best_match_idx}, 置信度: {best_score:.3f}") else: group_output.append("?") print(f"识别不确定: 最佳匹配 {best_match_idx}, 置信度: {best_score:.3f} (低于阈值 {CONFIG['match_threshold']})") return group_output def main(): """主函数""" print("开始数字识别处理...") print("=" * 50) # 列出当前目录内容,帮助用户确认文件位置 list_directory_contents() # 加载图像 print("\n=== 加载图像 ===") template_img = cv2.imdecode(np.fromfile(template_image_path, dtype=np.uint8), cv2.IMREAD_COLOR) input_img = cv2.imdecode(np.fromfile(input_image_path, dtype=np.uint8), cv2.IMREAD_COLOR) # 提取数字模板 print("\n=== 提取数字模板 ===") digits = extract_digit_templates(template_img) if not digits: print("错误:未能提取到任何数字模板") print("建议:检查模板图像是否包含清晰的数字") return # 预处理输入图像 print("\n=== 预处理输入图像 ===") tophat, thresh = preprocess_input_image(input_img) # 查找数字组 print("\n=== 查找数字组 ===") locs = find_digit_groups(thresh, input_img) if not locs: print("错误:未找到任何数字组") print("建议:调整参数或检查输入图像质量") return # 识别每个组中的数字 print("\n=== 识别数字 ===") output = [] result_img = input_img.copy() for (i, (gX, gY, gW, gH)) in enumerate(locs): print(f"\n处理第 {i+1} 个数字组...") # 提取组区域 group = tophat[gY:gY + gH, gX:gX + gW] # 识别组中的数字 group_output = recognize_digits_in_group(group, digits) if group_output: # 在结果图像上绘制 cv2.rectangle(result_img, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 2) result_text = "".join(group_output) cv2.putText(result_img, result_text, (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) output.extend(group_output) print(f"第 {i+1} 组识别结果: {result_text}") else: print(f"第 {i+1} 组识别失败") # 显示最终结果 print(f"\n=== 最终识别结果 ===") print(f"识别出的所有数字: {''.join(output)}") print("=" * 50) cv2.imshow("result", result_img) cv2.waitKey(0) cv2.destroyAllWindows() # 运行程序 if __name__ == "__main__": main() 如何修改可以使这个代码能实时识别摄像头的数字
最新发布
07-24
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值