OpenCV连续数字识别—可运行验证

前言

​ 文章开始,瞎说一点其他的东西,真的是很离谱,找了至少两三个小时,就一个简单的需求:

1、利用OpenCV 在Windows进行抓图

2、利用OpenCV 进行连续数字的检测。

3、使用C++,Qt

3、将检测的结果显示出来

​ 就这么简单的需求,结果网上找了各种版本硬是找不到,要是代码可能没啥问题,但是运行不了,你这运行不了,我怎么知道你到底能不能用,我代码调半天能用了,结果你跟我说最后效果不好,为啥呢?

​ 因为图像识别这种东西,很取决于你的外部环境的,一定你的外部环境变量,你的数字的背景啥的变了,那么你的代码肯定就要做相应的调整,这种不像深度学习能够自己学习的,实际只能靠你自己一步一步的去调试验证效果怎么样,最终得到适合你的。

​ 所以,我下面会给出我这个程序的打包的可直接验证效果的版本,你如果不是一个想调代码的人,或是你不是一个有耐心的人,或者你跟我的识别环境不一致, 那么我估计我的代码你也用不了,也不必去下载了。可继续找下一个了。

​ 但如果你说,只要我代码能让你运行起来,那么你就能够花精力把它调出来,实在不行,你让AI 帮你把它调出来,这都是没问题的,因为目前的运行方式很简单,只要你确保环境跟我一致,基本就没啥问题。

环境:

Windows 10

Qt 12.8 MSVC2015

OpenCV 4.5.5(我带的这个opencv 是用VS2015编译出来的,如果没有MSVC2015 ,那么就只能靠你自己去下载一个MinGW 之类的,或是你自己对应版本的OpenCV了)

运行现象:
在这里插入图片描述

因为这个是采用那个SVM首先进行模型训练的,我的模型,每个数字只放了一张或是两张,训练量太小了,出来的效果就比较不好,而且,若要进行这个识别,肯定要注意以下几点:

1、摄像头与数字的距离一定是固定的,然后外部光源也是固定的,不能说一会亮一会不亮的,这是不合理的。

2、需要拍摄更多组的照片以及数字来进行训练,甚至该模型可以采用自训练的方式,来进行优化,但我这个版本就没有做到这个点了,这个点有需要的可以来进行优化。后面对这个方面如果我有进行优化,我会来跟贴的。

3、可以对捕抓到的数字再进行一些处理,增大SVM训练的量,这样可能效果就会稳定很多了,我上面这个摄像头是手拿着的,所以会一直飘,我觉得应该也是比较正常的,毕竟只用了一天时间,搞出了这个demo,那效果肯定会有差强人意的地方。

可运行程序

通过网盘分享的文件:NumberRecognitionTool.zip
链接: https://pan.baidu.com/s/1hr8VqU2x17pIQ561hy8nQw?pwd=1111 提取码: 1111

我有试了一下,是可以运行的,如果不能运行可以留言下,我看下是什么原因。

如下,我会把我的核心代码给贴上去,如果有环境的,直接改一改运行就可以了。如果还觉得有点懒的话,可以直接下载我上传的资源文件,那里面我会把dll,啥的,都给你打包好,直接运行即可。不过要花费点积分就是了,如果又没有积分的话,可以加我qq,或者私信我,我可以直接发你。qq在主页有。

https://download.csdn.net/download/qq_43211060/90468759?spm=1001.2014.3001.5501

我也下载了好一些往上的资源,我也不知道有没有用,反正我没用上,如果有需要的话,也可以一起发给你们。希望能对你们有帮助。

正文

一、代码

处理的核心代码:

void CDataRecognitionMgr::InitSVM()
{srand((unsigned)time(0)); // 设置随机数种子// 定义数字图像尺寸:30x50digitWidth = 30;digitHeight = 50;hog = cv::HOGDescriptor(cv::Size(digitWidth, digitHeight), // winSizecv::Size(10, 10),                  // blockSizecv::Size(5, 5),                    // blockStridecv::Size(5, 5),                    // cellSize9                              // nbins);descriptorSize = (int)hog.getDescriptorSize();// ==========================// 1. 从外部加载模板图像,并生成数据增强后的训练样本// ==========================vector<Mat> trainImages;vector<int> trainLabels;const int numAugmentations = 100; // 每个数字至少生成 100 个训练样本for (int digit = 0; digit < 10; digit++) {// 模板图像存放在指定目录下(根据需要调整路径与图片格式)string folderPattern = "./img/Mod/" + to_string(digit) + "/*.png";vector<String> files;glob(folderPattern, files, false);if (files.empty()) {cout << "未找到数字 " << digit << " 的模板图片,请检查文件夹: " << folderPattern << endl;continue;}// 生成数据增强样本for (int i = 0; i < numAugmentations; i++) {// 随机选择一个模板图片int idx = rand() % files.size();Mat img = imread(files[idx], IMREAD_GRAYSCALE);if (img.empty()) {cout << "加载图片失败: " << files[idx] << endl;continue;}// 对模板图像进行增强处理Mat augImg = augmentImage(img, digitWidth, digitHeight);trainImages.push_back(augImg);trainLabels.push_back(digit);}}int totalSamples = (int)trainImages.size();if (totalSamples == 0) {cout << "未生成任何训练样本,请检查模板图像路径与数据增强处理!" << endl;return;}cout << "生成的训练样本总数: " << totalSamples << endl;// ==========================// 2. 构造训练数据矩阵// ==========================Mat trainingFeatures(totalSamples, descriptorSize, CV_32F);Mat trainingLabelsMat(totalSamples, 1, CV_32S);for (int i = 0; i < totalSamples; i++) {vector<float> descriptors;hog.compute(trainImages[i], descriptors);for (int j = 0; j < descriptorSize; j++) {trainingFeatures.at<float>(i, j) = descriptors[j];}trainingLabelsMat.at<int>(i, 0) = trainLabels[i];}// ==========================// 3. 使用 SVM(RBF 核)训练分类器// ==========================svm = SVM::create();svm->setType(SVM::C_SVC);svm->setKernel(SVM::RBF);svm->setC(2.0);svm->setGamma(0.005);svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6));cout << "开始训练 SVM..." << endl;svm->train(trainingFeatures, ml::ROW_SAMPLE, trainingLabelsMat);cout << "SVM 训练完成。" << endl;
}void CDataRecognitionMgr::HandlerImage(const QImage &_oImg)
{
#if 1Mat mat = _ImageToMat(_oImg);Mat matGray;cvtColor(mat, matGray, COLOR_BGR2GRAY);Mat testImgThresh;threshold(matGray, testImgThresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//    imshow("testImgThresh",testImgThresh);Mat struct1;struct1=getStructuringElement(0,Size(2,2));//矩形结构元素Mat erodeSrc;//存放腐蚀后的图像erode(testImgThresh, erodeSrc,struct1);Mat morphKernel = getStructuringElement(MORPH_RECT, Size(3, 3));morphologyEx(erodeSrc, testImgThresh, MORPH_OPEN, morphKernel);morphologyEx(erodeSrc, testImgThresh, MORPH_CLOSE, morphKernel);vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(testImgThresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);qDebug() << "---> contours:"<<contours.size();if (contours.size() < 10){return;}vector<Rect> digitROIs;for (const auto& contour : contours) {Rect bbox = boundingRect(contour);// 根据尺寸过滤噪声与无效区域qDebug() << "---> bbox.width:"<<bbox.width<<";bbox.height:"<<bbox.height;if (bbox.width > 20 && bbox.height > 20 && bbox.width < 200 && bbox.height < 200) {digitROIs.push_back(bbox);}}// 3. 分割粘连区域int avgWidth = 90; // 假设单个数字的平均宽度,可根据实际情况调整for (size_t i = 0; i < digitROIs.size(); i++) {if (digitROIs[i].width > 1.5 * avgWidth) { // 判断是否为粘连区域// 提取粘连区域的二值图像Mat roiImg = testImgThresh(digitROIs[i]);// 计算垂直投影Mat projection(1, roiImg.cols, CV_32F);reduce(roiImg, projection, 0, REDUCE_SUM, CV_32F);// 寻找分割点(局部最小值)int splitPos = -1;float minVal = numeric_limits<float>::max();for (int j = 1; j < projection.cols - 1; j++) {float val = projection.at<float>(0, j);if (val < projection.at<float>(0, j - 1) && val < projection.at<float>(0, j + 1) && val < minVal) {minVal = val;splitPos = j;}}// 根据分割点分割边界框if (splitPos > 0) {Rect leftROI(digitROIs[i].x, digitROIs[i].y, splitPos, digitROIs[i].height);Rect rightROI(digitROIs[i].x + splitPos, digitROIs[i].y, digitROIs[i].width - splitPos, digitROIs[i].height);// 替换原始粘连区域digitROIs.erase(digitROIs.begin() + i);digitROIs.insert(digitROIs.begin() + i, leftROI);digitROIs.insert(digitROIs.begin() + i + 1, rightROI);i--; // 重新检查新插入的区域}}}// 按 x 坐标排序(从左到右)sort(digitROIs.begin(), digitROIs.end(), [](const Rect& a, const Rect& b) {return a.x < b.x;});cout << "检测到的轮廓数量: " << digitROIs.size() << endl;for (const auto& roi : digitROIs) {cout << "边界框: " << roi << endl;}string recognized = "";for (const auto& roi : digitROIs) {Mat digitROI = testImgThresh(roi);Mat digitResized;resize(digitROI, digitResized, Size(digitWidth, digitHeight));vector<float> descriptors;hog.compute(digitResized, descriptors);Mat sample(1, descriptorSize, CV_32F);for (int j = 0; j < descriptorSize; j++) {sample.at<float>(0, j) = descriptors[j];}int predicted = (int)svm->predict(sample);recognized.push_back('0' + predicted);}QString str = QString::fromStdString(recognized);emit SIGNAL_DATA_NUM(str);cout << "识别结果1: " << recognized << endl;
#endif
}

InitSVM基本就是训练的标准流程了,那么比较核心的还是下面这个函数,这个函数HandlerImage可能就需要你进行一些调整:
首先先进行基本的图像处理,由于某些打印的会出现说数字粘在一起的情况,那么就得采用这个分割粘连区域进行局部处理,才能分割出来,我这份代码试了两种情况,都还可以,一个是会粘着的,一个是不会粘着的。

其他你需要更详细的,可以将这两个函数放到AI中帮忙解释一下就可以了。

接下来,就到了我们的经典环节:
在这里插入图片描述

在这里插入图片描述

参考

1、opencv 数字识别 数码管

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/72904.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/72904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

shell的模拟实现 ─── linux第16课

在shell的命令行中输入命令,会有两种执行命令的途径 shell自己执行 shell创建子进程(fork ,exit ,waitpid,exec) ,子进程去执行 shell自己执行的命令是自建命令(bulit command) 子进程执行的是非自建命令 第一版只能维护命令行参数表创建子进程, 执行非内建命令 我们先创…

MySQL创建数据库和表,插入四大名著中的人物

一、登录数据库并创建数据库db_ck 二、创建表t_hero 表属性包括&#xff08;id&#xff0c;name&#xff0c;nickname&#xff0c;age&#xff0c;gender&#xff0c;address&#xff0c;weapon&#xff0c;types&#xff09; mysql> create table t_hero(-> id int,-…

静态网页的爬虫(以电影天堂为例)

一、电影天堂的网址&#xff08;url&#xff09; 电影天堂_免费电影_迅雷电影下载_电影天堂网最好的迅雷电影下载网&#xff0c;分享最新电影&#xff0c;高清电影、综艺、动漫、电视剧等下载&#xff01;https://dydytt.net/index.htm 我们要爬取这个页面上的内容 二、代码…

【C++】:STL详解 —— 红黑树封装map和set

目录 红黑树的源代码 正向迭代器的代码 反向迭代器的代码 set的模拟实现 map的模拟实现 红黑树的源代码 #pragma once #include <iostream>using namespace std; // set ->key // map ->key/value// set ->key // map ->key/valueenum Colour {RED,BLAC…

MATLAB控制函数测试要点剖析

一、功能准确性检验 基础功能核验 针对常用控制函数&#xff0c;像用于传递函数建模的 tf 、构建状态空间模型的 ss &#xff0c;以及开展阶跃响应分析的 step 等&#xff0c;必须确认其能精准执行基础操作。以 tf 函数为例&#xff0c;在输入分子与分母系数后&#xff0c;理…

MoonSharp 文档一

目录 1.Getting Started 步骤1&#xff1a;在 IDE 中引入 MoonSharp 步骤2&#xff1a;引入命名空间 步骤3&#xff1a;调用脚本 步骤4&#xff1a;运行代码 2.Keeping a Script around 步骤1&#xff1a;复现前教程所有操作 步骤2&#xff1a;改为创建Script对象 步骤…

ROS云课三分钟-差动移动机器人导航报告如何撰写-及格边缘疯狂试探

提示词&#xff1a;基于如上所有案例并结合roslaunch teb_local_planner_tutorials robot_diff_drive_in_stage.launch和上面所有对话内容&#xff0c;设计一个差速移动机器人仿真实验&#xff0c;并完成报告的全文撰写。 差速移动机器人导航仿真实验报告 一、实验目的 验证 T…

ACE协议学习1

在多核系统或复杂SoC&#xff08;System on Chip&#xff09;中&#xff0c;不同处理器核心或IP&#xff08;Intellectual Property&#xff09;模块之间需要保持数据的一致性。常用的是ACE协议or CHI。 先对ACE协议进行学习 ACE协议&#xff08;Advanced Microcontroller Bu…

ajax之生成一个ajax的demo示例

目录 一. node.js和express ​二. 使用express创建后端服务 三. 创建前端 一. node.js和express ajax是前端在不刷新的情况下访问后端的技术&#xff0c;所以首先需要配置一个后端服务&#xff0c;可以使用node.js和express。 首先生成一个空项目&#xff0c;新建main目录…

Java 字节码操纵框架 -ASM

Java 字节码操纵框架 -ASM 1.ASM 概述: ASM 是用于 Java 字节码操纵的框架,可动态生成新类或增强现有类的功能。它既能直接产生二进制 class 文件,也能在类被加载到虚拟机之前动态改变类行为,通过读取类文件信息来分析、修改类行为,甚至生成新类。许多流行框架如 cglib、…

kafka + flink +mysql 案例

假设你有两个Kafka主题&#xff1a;user_activities_topic 和 product_views_topic&#xff0c;并且你希望将user_activities_topic中的数据写入到user_activities表&#xff0c;而将product_views_topic中的数据写入到product_views表。 maven <dependencies><!-- …

远程登录客户端软件 CTerm 发布了 v4.0.0

有时候我们需要远程登录到 Linux/Unix 服务器&#xff0c;这方面使用最广泛的客户端软件是 PuTTY&#xff0c;不过它是全英文的&#xff0c;而且是单窗口的&#xff0c;有时候显得不那么方便。 CTerm (Clever Terminal) 是一个 Windows 平台下支持 Telnet 和 SSH 协议进行远程…

从李佳琦团队看新型用工:灵活就业如何重构组织架构?

2022年“双11”期间&#xff0c;李佳琦直播间累计销售额突破115亿元&#xff08;来源&#xff1a;新腕数据《2022双11直播电商战报》&#xff09;&#xff0c;其背后团队规模约400人&#xff0c;但全职员工仅占35%&#xff0c;其余65%为外包选品团队、兼职客服、第三方MCN机构人…

微软程序的打包格式MSIX

MSIX 微软推出的MSIX格式是其为统一Windows应用程序打包和部署而设计的新一代安装包格式&#xff0c;具有以下核心特点和进展&#xff1a; 1. 推出背景与时间线 MSIX最初于2018年在微软Build大会上宣布&#xff0c;并在同年7月发布预览版打包工具&#xff0c;10月正式版上线…

AFL++安装

学习fuzzing也几天了&#xff0c;今天记录AFL的安装及使用 一、实验环境 虚拟机&#xff1a;ubuntu20.04 当然也可以uname -a去看自己的版本号 二、AFL安装 1.先更新一下工具 sudo apt update2.安装AFL必要的一些依赖&#xff0c;例如编译工具&#xff08;如 build-essen…

【STM32】ADC功能-单通道多通道(学习笔记)

本章结合上一节内容复习更好理解【江协科技STM32】ADC数模转换器-学习笔记-CSDN博客 一、ADC单通道 接线图 ADC初始化 ①RCC开启时钟&#xff0c;包括ADC和GPIO的时钟&#xff0c;另外ADCCLK的分频器也要配置 ②配置GPIO,&#xff0c;把需要用的GPIO配置成模拟输入模式&am…

基于YOLO11深度学习的运动品牌LOGO检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

当前主流的大模型训练与推理框架的全面汇总

以下是当前主流的大模型训练与推理框架的全面汇总 以下是更新后包含 SGLang 的大模型训练与推理框架列表&#xff0c;并对分类和示例进行了优化&#xff1a; 一、通用深度学习推理框架 TensorRT-LLM 特点&#xff1a;NVIDIA推出的针对Transformer类模型的优化框架&#xff0c;支…

Linux学习(八)(服务管理(检查服务状态,开始/停止服务,检查服务日志,创建新服务))

服务管理 Linux 中的服务管理是指控制 Linux 在启动和关闭计算机的过程中启动和停止的服务&#xff08;或“守护程序”&#xff09;的系统。这些服务执行各种功能&#xff0c;并提供未附加到用户界面的进程。 Linux 系统&#xff0c;尤其是系统管理员&#xff0c;通常需要管理…

ElasticSearch 分词器介绍及测试:Standard(标准分词器)、English(英文分词器)、Chinese(中文分词器)、IK(IK 分词器)

ElasticSearch 分词器介绍及测试&#xff1a;Standard&#xff08;标准分词器&#xff09;、English&#xff08;英文分词器&#xff09;、Chinese&#xff08;中文分词器&#xff09;、IK&#xff08;IK 分词器&#xff09; ElasticSearch 分词器介绍及测试1. Standard Analyz…