【openCV】手写算式识别

OpenCV 机器学习库提供了一系列 SVM 函数和类来实现 SVM 模型的训练和预测,方便用户实现自己的 SVM 模型,并应用于分类问题。本文主要介绍使用 openCV 实现手写算式识别的工作原理与实现过程。

目录

1 SVM 模型

1.1 SVM 模型介绍

1.2 SVM 模型原理

2 手写算式识别

2.1 字符识别

2.2 算式识别


1 SVM 模型

1.1 SVM 模型介绍

        SVM 是支持向量机(Support Vector Machine)的英文缩写,是统计学习理论中一种重要的分类方法,其早期工作来自前苏联 Vladimir N. Vapnik 和 Alexander Y. Lerner 在1963年发表的研究。

        1995年,Corinna Cortes 和 Vapnik 提出了软边距的非线性 SVM 并将其应用于手写字符识别问题,为 SVM 在其他领域的应用提供了参考。

SVM 的优点主要包括:

​    1)具有较好的可解释性。SVM 的决策函数和支持向量清晰,易于理解。

​    2)适用性广泛。SVM 能够应用于多种数据类型和领域,如文本分类、图像识别和生物信息学等。

​    3)鲁棒性强。SVM 对训练数据中的噪声和异常点具有较强的容错能力,能有效处理输入数据中的噪声。

​    4)适合高维数据。通过核函数,SVM 能够将低维空间的非线性问题映射到高维空间,进行线性划分,从而解决复杂的非线性问题。

​    5)可控制的过拟合。通过调整正则化参数和松弛变量,SVM 可以控制模型的复杂度,有效避免过拟合问题。

​    6)避免陷入局部最优解。使用结构风险最小化原则,使得 SVM 能够更好地避免陷入局部最优解,并具有较低的泛化误差。

1.2 SVM 模型原理

        在二分类问题中,给定输入数据和学习目标: X=\{ X_1, X_2, ... , X_N\}y \in \{-1, 1\},若存在决策边界(decision boundary)

\omega ^T X + b = 0

将样本按类别分开,则称该分类问题是线性可分的(Linear Separable)。

        按照统计学习理论,分类器在经过学习新数据时会产生风险,风险的类型分为经验风险和结构风险:

式中 f 表示分类器,经验风险由损失函数定义,描述了分类器所给出的分类结果的准确程度;结构风险由分类器参数矩阵的范数定义,描述了分类器自身的复杂程度以及稳定程度。

        复杂的分类器容易过拟合,因此是不稳定的。通过最小化经验风险和结构风险的线性组合以确定其模型参数:

式中 C 是正则化参数,当 p = 2 时,该式被称为 L_2 正则化。

​    对于线性可分问题,SVM 经验风险为 0,SVM 模型简化为最小化结构风险,由于点到超平面的距离反比于 || ω ||,因此模型可解释为最大化样本到超平面的最小距离,

即最优超平面距离给定的每个样本尽可能远。

2 手写算式识别

2.1 字符识别

        OpenCV 机器学习库提供了一系列 SVM 函数和类来实现 SVM 模型的训练和预测,可以很方便地实现用户自定义的分类模型。

使用 OpenCV 实现 SVM 模型的基本步骤如下:

    (1)创建模型。使用 cv2.ml.SVM_create() 创建 SVM 模型,使用 setKernel() 指定核函数;

    (2)初始化模型参数。使用 setC() 和 setGamma() 设置参数的初始值;

    (3)模型训练。使用 train() 函数,以及向量化的样本和分类标签,训练模型;

    (4)模型评估。使用 predict() 预测新样本,并统计正确率;

    (5)模型保存。使用 save() 保存模型,文件格式为 *.dat 。

        在手写算式的字符识别中,需要识别数字 0 ~ 9,以及 +,-,×,÷,(,)和 = 共 17 种字符。SVM 模型的输入样本是字符图像向量化的结果,处理步骤包括:

      1)图像缩放。将字符图像统一成 28 × 28 大小;

      2)颜色反转。使用 cv2.bitwise_not() 函数实现颜色反转,便于后续步骤;

      3)去偏斜。使用 cv2.moments() 计算图像的矩,然后使用 cv2.warpAffine() 去偏斜;

      4)向量化。将图像按照十字划分成 4 个区域,计算每个区域的方向梯度直方图,拼接成一个向量。

参考链接:OpenCV: OCR of Hand-written Data using SVM

2.2 算式识别

        手写算式识别包括 3 个阶段:字符分割、图像预处理和字符识别。字符分割用于提取输入图像中的连续字符,图像预处理用于字符图像的特征化,字符识别用于图像与字符的对应。最后按照顺序拼接识别到的字符,就得到输出表达式。

#-*- Coding: utf-8 -*-import cv2
import numpy as np
import gradio as gr# 加载模型
model = cv2.ml.SVM_load('./svm_data.dat')
chars = '0123456789+-*/()='SZ = 28
bin_n = 16 # Number of binsdef resize(src_img, size):# 获取原图像的宽、高h, w = src_img.shapeif h >= size and w >= size:# 图像缩放dst_img = cv2.resize(src_img, (size, size), interpolation=cv2.INTER_CUBIC)elif h >= size:# 填充左右边缘dst_img = np.zeros(shape=(h, size), dtype=np.uint8)dst_img[:, (size-w)//2:(size-w)//2+w] = src_imgdst_img = cv2.resize(dst_img, (size, size), interpolation=cv2.INTER_CUBIC)elif w >= size:# 填充上下边缘dst_img = np.zeros(shape=(size, w), dtype=np.uint8)dst_img[(size-h)//2:(size-h)//2+h, :] = src_imgdst_img = cv2.resize(dst_img, (size, size), interpolation=cv2.INTER_CUBIC)else:# 填充四周dst_img = np.zeros(shape=(size, size), dtype=np.uint8)dst_img[(size-h)//2:(size-h)//2+h, (size-w)//2:(size-w)//2+w] = src_imgreturn dst_imgdef deskew(src_img):"""Deskew the image using its second order moments"""m = cv2.moments(src_img)if abs(m['mu02']) < 1e-2:return src_img.copy()skew = m['mu11']/m['mu02']M = np.float32([[1, -skew, 0.5*SZ*skew], [0, 1, 0]])dst_img = cv2.warpAffine(src_img, M, (SZ, SZ), cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)return dst_imgdef hog(image):gx = cv2.Sobel(image, cv2.CV_32F, 1, 0)gy = cv2.Sobel(image, cv2.CV_32F, 0, 1)mag, ang = cv2.cartToPolar(gx, gy)bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0, ..., 16)bin_cells = bins[:14,:14], bins[14:,:14], bins[:14,14:], bins[14:,14:]mag_cells = mag[:14,:14], mag[14:,:14], mag[:14,14:], mag[14:,14:]hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]hist = np.hstack(hists) # hist is a 64bit vectorreturn histdef pre_process(src_img):"""图像预处理"""img_resize = resize(src_img, SZ)img_invert = cv2.bitwise_not(img_resize) # 颜色翻转img_deskew = deskew(img_invert)hist = hog(img_deskew)return histdef exprRecognize(src_img, filter_size):"""手写算式识别"""# 灰度图gray = cv2.cvtColor(src_img, cv2.COLOR_BGR2GRAY)# 二值化_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)binary_inv = cv2.bitwise_not(binary)# 中值滤波filter_size = int(filter_size[0][0]) if filter_size else 3binary_f = cv2.medianBlur(binary_inv, filter_size)# 查找字符区域contours, _ = cv2.findContours(binary_f, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 遍历所有区域,寻找最大宽度w_max = 0for cnt in contours:_, _, w, _ = cv2.boundingRect(cnt)if w > w_max:w_max = w# 遍历所有区域,拼接x坐标接近的区域char_dict = {}for cnt in contours:x, y, w, h = cv2.boundingRect(cnt)x_mid = x + w//2 # 计算中点位置if not char_dict.keys() or all(np.abs(z - x_mid) > w_max/1.5 for z in char_dict.keys()):char_dict[x_mid] = cntelse:for z in char_dict.keys():if np.abs(z - x_mid) <= w_max/1.5:char_dict[z] = np.concatenate((char_dict[z], cnt), axis=0) # 拼接两个区域# 按照中点坐标,对字符进行排序char_dict = dict(sorted(char_dict.items(), key=lambda item: item[0]))# 遍历所有区域,提取字符dst_img = []for _, cnt in char_dict.items():x, y, w, h = cv2.boundingRect(cnt)roi = binary[y:y+h, x:x+w]dst_img.append(roi)expr = ''for char in dst_img:hist = pre_process(char)hist = np.array(hist, dtype=np.float32)result = model.predict(hist.reshape(-1, 4*bin_n))[1]expr += chars[int(result[0])]return dst_img, expr, eval(expr.replace('=', ''))if __name__ == "__main__":demo = gr.Interface(fn=exprRecognize,inputs=[gr.Image(label="input image"), gr.Radio(['3x3', '5x5', '7x7'], value='3x3')],outputs=[gr.Gallery(label="charset", columns=[3], object_fit="contain", height="auto"),gr.Text(label="expression"),gr.Text(label="result")],live=True)demo.launch()

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

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

相关文章

在DelayMS加入bsp_Idle,把单片机延时空闲利用起来

在单片机应用中&#xff0c;使用延时函数 DelayMS() 会导致程序在延时期间无法执行其他任务&#xff0c; 这可能影响系统对一些响应时间要求较高的任务的处理。 为了提高系统的响应速度和利用单片机的空闲时间&#xff0c;可以在延时函数中加入 bsp_Idle() 函数&#xff0c; 以…

3.21系统栈、数据结构栈、栈的基本操作、队列、队列的基本操作------------》

栈 先进后出、后进先出 一、系统栈 大小&#xff1a;8MB 1、局部变量 2、未经初始化为随机值 3、代码执行到变量定义时为变量开辟空间 4、当变量的作用域结束时回收空间 5、函数的形参和返回值 6、函数的调用关系、保护现场和恢复现场 7、栈的增长方向&#xff0c;自高…

5.1.5、【AI技术新纪元:Spring AI解码】HuggingFace Chat

HuggingFace 推理端点允许您在云中部署和提供机器学习模型,使其可以通过 API 访问。 入门 关于 HuggingFace 推理端点的更多详细信息可以在此处找到。 先决条件 添加 spring-ai-huggingface 依赖项: <dependency><groupId>org.springframework.ai</groupId…

【Linux进程的状态】

目录 看Linux源码中的说法 如何查看进程状态&#xff1f; 各个状态的关系 僵尸进程 举个栗子 现象 僵尸进程的危害 孤儿进程 举个栗子 现象 进程的优先级 基本概念 为什么要有进程优先级&#xff1f; 查看系统进程 进程的大致属性 进程优先级vs进程的权限 Linu…

[Semi-笔记] 2023_TIP

目录 概要一&#xff1a;Conservative-Progressive Collaborative Learning&#xff08;保守渐进式协作学习&#xff09;挑战&#xff1a;解决&#xff1a; 二&#xff1a;Pseudo Label Determination for Disagreement&#xff08;伪标签分歧判定&#xff09;挑战&#xff1a;…

利用python进行接口测试及类型介绍

前言 其实我觉得接口测试很简单&#xff0c;比一般的功能测试还简单&#xff08;这话我先这样说&#xff0c;以后可能会删O(∩_∩)O哈&#xff01;&#xff09;&#xff0c;现在找工作好多公司都要求有接口测试经验&#xff0c;也有好多人问我&#xff08;也就两三个人&#x…

python离线安装依赖库 依赖库版本

问题&#xff1a;python开发过程中&#xff0c;下载安装某一功能模块&#xff0c;常常需要各种依赖库支持。如果在线安装&#xff0c;则非常方便简单&#xff0c;但往往需要离线下载所需的依赖库。但各种依赖库存在许多版本&#xff0c;如何准确快速知道我们所需要的依赖库呢&a…

解决微信小程序代码包大小限制方法

1 为什么微信小程序单个包的大小限制为 2MB 微信小程序单个包的大小限制为 2MB 是出于以下几个考虑&#xff1a; 保证小程序的启动速度&#xff1a;小程序的启动速度是影响用户体验的关键因素之一。如果包太大&#xff0c;会导致小程序启动时间过长&#xff0c;从而影响用户体…

node安装

这里写目录标题 https://nodejs.cn/ https://registry.npmmirror.com/binary.html?pathnode/ https://registry.npmmirror.com/binary.html?pathnode/v11.0.0/

初识CSS样式 与 文本背景样式

目录 前言: 1.什么是CSS: 2.关于css的主要特性: 2.1层叠性&#xff1a; 2.2继承性&#xff1a; 2.3优先级&#xff1a; 2.4.CSS的组成结构: 3.css样式的三种写法: 3.1内联样式&#xff1a; 3.1.2存在的优点和缺点: 3.2内部样式表&#xff1a; 3.2.2存在的优点和缺点:…

JAVA 100道题(11)

11.使用ArrayList存储一组整数&#xff0c;并编写一个方法打印所有偶数。 它使用ArrayList来存储一组整数&#xff0c;并编写一个方法来打印所有的偶数。 java复制代码 import java.util.ArrayList; public class Main { public static void main(String[] args) { // 创建一个…

[蓝桥杯 2019 省 A] 修改数组

题目链接 [蓝桥杯 2019 省 A] 修改数组 题目描述 给定一个长度为 N N N 的数组 A [ A 1 , A 2 , A 3 , . . . , A N ] A [A_1, A_2, A_3, ...,A_N] A[A1​,A2​,A3​,...,AN​]&#xff0c;数组中有可能有重复出现的整数。 现在小明要按以下方法将其修改为没有重复整数的…

Java实现Cache Aside Pattern 例子

简单的Java实现Cache Aside pattern &#xff1a; import java.util.HashMap; import java.util.Map;public class CacheAsidePattern {// 模拟数据库或其他数据源static class Database {private static Map<String, String> data new HashMap<>();static {data…

CAN总线Stuff Error

CAN总线Stuff Error&#xff0c;即填充错误&#xff0c;指的是在使用位填充编码的位流中&#xff0c;出现了第六个连续相同的位电平。这种错误通常发生在位填充机制被用于CAN总线通信中&#xff0c;以确保数据传输的正确性和稳定性。以下是一些可能导致Stuff Error的原因&#…

每日三个JAVA经典面试题(十九)

1.Java Concurrency API 中的 Lock 接口(Lock interface)是什么&#xff1f;对比同步它有什么优势&#xff1f;Java并发API中的Lock接口提供了一种比传统synchronized块或方法更灵活、更强大的线程同步机制。Lock接口允许更细粒度的锁控制&#xff0c;通过它可以实现更复杂的线…

一、rv1126开发之视频输入和视频编码

RV1126 H264/HEVC编码流程 一、RV1126编码的流程图&#xff1a; 二、每个代码模块详细讲解 2.1. VI模块的创建 VI模块的初始化&#xff1a;关键在于VI_CHN_ATTR_S结构体&#xff0c;这个结构体是VI设置的结构体。这个结构体的成员变量包括&#xff1a;pcVideoNode&#xff0…

php实现二进制权限控制示例,二进制权限控制实现思路,二进制权限控制逻辑,二进制权限控制好处,二进制权限控制弊端,二进制权限控制存权限查权限

背景:在群里看到大佬们分享这个知识点自己实操了一下做了个总结 1.二进制权限控制实现原理思路及逻辑 对于二进制权限控制,一个常见的做法是将权限用一个整数表示,然后将这个整数转换为二进制进行存储和查找。以下是一种简单的实现思路: 存储权限信息: 将权限信息转换为…

震惊!!!原来这就是操作系统

震惊!!!原来这就是操作系统 一:什么是操作系统1:管理2:操作系统的组成 二:进程:1:概念2:进程的管理方式2.1:描述2.2:组织 一:什么是操作系统 CPU,内存,硬盘…这些都是硬件,而操作系统就是软件,可以让计算机按照一定的规则进行执行. 软件是一组指令的集合, 1:管理 **操作系统…

flask之请求钩子

请求钩子是通过装饰器的形式实现&#xff0c;Flask支持如下四种请求钩子&#xff1a; 1、before_first_request: 在第一次请求处理之前先被执行 2、before_request: 在每次请求前执行 3、after_request: 在每次请求处理之后被执行 接受一个参数&#xff1a;视图函数的响应在…

[力扣 129]求根节点到叶节点之和

题目描述&#xff1a; 思路&#xff1a; 可以采用递归回溯。递归访问左->右->根节点并记录路径。到叶节点后&#xff0c;计算数字并相加。 代码&#xff1a; class Solution:def sumNumbers(self, root: TreeNode) -> int:res 0path []def backtrace(root):nonl…