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 数字识别 数码管
相关文章:

OpenCV连续数字识别—可运行验证
前言 文章开始,瞎说一点其他的东西,真的是很离谱,找了至少两三个小时,就一个简单的需求: 1、利用OpenCV 在Windows进行抓图 2、利用OpenCV 进行连续数字的检测。 3、使用C,Qt 3、将检测的结果显示出来 …...
Python中与字符串操作相关的30个常用函数及其示例
以下是Python中与字符串操作相关的30个常用函数及其示例: 1. str.capitalize() 将字符串的第一个字符大写,其余字符小写。 s "hello world" print(s.capitalize()) # 输出: Hello world2. str.lower() 将字符串中的所有字符转换为小写。…...

007-Property在C++中的实现与应用
Property在C中的实现与应用 以下是在C中实现属性(Property)的完整实现方案,结合模板技术和运算符重载实现类型安全的属性访问,支持独立模块化封装: #include <iostream> #include <functional>template<typename HostType, t…...

【实战篇】【DeepSeek 全攻略:从入门到进阶,再到高级应用】
凌晨三点,某程序员在Stack Overflow上发出灵魂拷问:“为什么我的DeepSeek会把财务报表生成成修仙小说?” 这个魔性的AI工具,今天我们就来场从开机键到改造人类文明的硬核教学。(文末含高危操作集锦,未成年人请在师父陪同下观看) 一、萌新村任务:把你的电脑变成炼丹炉 …...

clickhouse属于国产吗
《ClickHouse:探索其背景与国内的应用实例》 当我们谈论数据库技术时,ClickHouse是一个绕不开的话题。很多人可能会好奇,ClickHouse是否属于国产软件呢?答案是,虽然ClickHouse最初并非在中国开发,但这款列…...

ESP32 UART select解析json数据,上位机控制LED灯实验
前言: 本实验的目的主要是通过上位机通过UART来控制ESP32端的LED的点亮以及熄灭,整个项目逻辑比较简单,整体架构如下: 上位机(PC)主要是跑在PC端的一个软件,主要作用包含: 1)串口相关配置&…...

K8S 集群搭建——cri-dockerd版
目录 一、工作准备 1.配置主机名 2.配置hosts解析 3.配置免密登录(只需要在master上操作) 4.时间同步(每台节点都要做,必做,否则可能会因为时间不同步导致集群初始化失败) 5.关闭系统防火墙 6.配置…...
基于Python的电商销售数据分析与可视化系统实
一、系统架构设计 1.1系统流程图 #mermaid-svg-Pdo9oZWrVHNuOoTT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-icon{fill:#552222;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-text{fill:#5…...

学习笔记:Python网络编程初探之基本概念(一)
一、网络目的 让你设备上的数据和其他设备上进行共享,使用网络能够把多方链接在一起,然后可以进行数据传递。 网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信。 二、IP地址的作用 用来标记唯一一台电脑…...
高效处理 List<T> 集合:更新、查找与优化技巧
引言 在日常开发中,List<T> 是我们最常用的数据结构之一。无论是批量更新数据、查找特定项还是进行复杂的集合操作,掌握 List<T> 的高级用法可以显著提高代码的效率和可读性。本文将详细介绍如何使用 List<T> 进行批量更新、查找匹配项以及优化性能的方法…...

HTML5(Web前端开发笔记第一期)
p.s.这是萌新自己自学总结的笔记,如果想学习得更透彻的话还是请去看大佬的讲解 目录 三件套标签标题标签段落标签文本格式化标签图像标签超链接标签锚点链接默认链接地址 音频标签视频标签 HTML基本骨架综合案例->个人简介列表表格表单input标签单选框radio上传…...
Windows控制台函数:标准输入输出流交互函数GetStdHandle()
目录 什么是 GetStdHandle? 它长什么样? 怎么用它? 它跟 std::cout 有什么不一样? GetStdHandle 是一个 Windows API 函数,用于获取标准输入、标准输出或标准错误设备的句柄。它定义在 Windows 的核心头文件 <…...
Vue3 中 Computed 用法
Computed 又被称作计算属性,用于动态的根据某个值或某些值的变化,来产生对应的变化,computed 具有缓存性,当无关值变化时,不会引起 computed 声明值的变化。 产生一个新的变量并挂载到 vue 实例上去。 vue3 中 的 com…...
常见的三种锁
一、互斥锁 互斥锁 Mutex 保证在任意时刻只有一个线程能够进入被保护的临界区。当一个线程获取到互斥锁后,其他线程若要进入临界区就会被阻塞,直到该线程释放锁。 互斥锁是一种阻塞锁,当线程无法获取到锁时,会进入阻塞状态。 应…...

离线文本转语音库pyttsx3(目前接触到的声音效果最好的,基本上拿来就能用)
在现代应用程序中,文本转语音(Text-to-Speech, TTS)技术越来越受到重视。无论是为视力障碍人士提供帮助,还是为教育和娱乐应用增添趣味,TTS 都能发挥重要作用。今天,我们将介绍一个简单易用的 Python 库——…...

LeetCode Hot100刷题——反转链表(迭代+递归)
206.反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出:[2,1]示例 3&#…...
JJJ:linux sysfs相关
文章目录 1.sysfs(属性)文件的创建、读、写1.1 创建流程1.2 open流程1.3 read流程 2.补充2.1 sysfs下常见目录介绍2.2 属性相关2.2.1 简介2.2.2 attribute文件的创建 2.3 sysfs目录如何创建的 1.sysfs(属性)文件的创建、读、写 1…...

Leetcode 刷题记录 06 —— 矩阵
本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答。 目录 01 矩阵置零 方法一:标记数组 方法二:两个标记变量 02 螺旋矩阵…...

什么样的物联网框架适合开展共享自助KTV唱歌项目?
现在物联网的广泛应用,也让更多用户们看到了它的实力,也使得共享经济遍地开花。其中共享自助唱歌设备也备受欢迎,那么适合开展共享自助KTV唱歌项目的物联网框架都应具备哪些特点呢? 智能化与自动化管理 物联网技术在共享KTV中的应…...
【Academy】HTTP Host 标头攻击 ------ HTTP Host header attacks
HTTP Host 标头攻击 ------ HTTP Host header attacks 1. 什么是 HTTP Host 标头?2. 什么是 HTTP Host 标头攻击?3. HTTP Host 标头漏洞是如何产生的?4. 如何测试 HTTP Host 标头漏洞4.1 提供任意 Host 标头4.2 检查有缺陷的验证4.3 发送不明…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...