【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 bins


def resize(src_img, size):
   # 获取原图像的宽、高
   h, w = src_img.shape

   if 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_img
      dst_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_img
      dst_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_img

   return dst_img


def 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_img


def 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 vector
   return hist


def 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 hist


def 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 3
   binary_f = cv2.medianBlur(binary_inv, filter_size)

   # 查找字符区域
   contours, _ = cv2.findContours(binary_f, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

   # 遍历所有区域,寻找最大宽度
   w_max = 0
   for 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] = cnt
      else:
         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()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用广播信道的数据链路层

目录 一、局域网的特点 二、媒体共享技术 三、以太网的两个标准 四、以太网 五、CSM/CD协议 1、碰撞检测 2、争用期 3、CSMA/CD重要特性 4、CSMA/CD协议的要点 六、小结 一、局域网的特点 局域网具有如下主要优点&#xff1a; • 具有广播功能&#xff0c; 从一…

Linux系统Docker安装Drupal并配置数据库实现公网远程访问本地站点

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它的学习…

【07】进阶html5

HTML5 包含两个部分的更新,分别是文档和web api 文档 HTML5 元素表 元素语义化 元素语义化是指每个 HTML 元素都代表着某种含义,在开发中应该根据元素含义选择元素 元素语义化的好处: 利于 SEO(搜索引擎优化)利于无障碍访问利于浏览器的插件分析网页新增元素 多媒体…

Spring6--基础概念

1. 概述 1.1. Spring是什么 Spring 是一套广泛应用于 Java 企业级应用开发领域的轻量级开源框架&#xff0c;由 Rod Johnson 创立&#xff0c;旨在显著降低 Java 企业应用的复杂性&#xff0c;缩短开发周期&#xff0c;并提升开发效率。Spring 不仅适用于服务器端开发&#x…

Lenze伦茨8400变频器E84A L-force Drives 操作使用说明

Lenze伦茨8400变频器E84A L-force Drives 操作使用说明

html5cssjs代码 035 课程表

html5&css&js代码 035 课程表 一、代码二、解释基本结构示例代码常用属性样式和装饰响应式表格辅助技术 一个具有亮蓝色背景的网页&#xff0c;其中包含一个样式化的表格用于展示一周课程安排。表格设计了交替行颜色、鼠标悬停效果以及亮色表头&#xff0c;并对单元格设…

关于alias、root的用法

关于alias、root的用法 root 语法&#xff1a;root path 默认值&#xff1a; root html 配置段&#xff1a; http,server,location,if 例子&#xff1a; 静态文件地址&#xff1a;/home/static/html/js/demo.html 用例1&#xff1a; 以请求http://example.com/js/demo.html为…

指路明灯,99%自动化测试从业者都该看的职业规划!

这篇文章将从以下三个方面来给大家介绍自动化测试&#xff0c;其中包含自动化测试从业者需要了解的知识和一些常见的思想误区&#xff0c;以及自动化测试行业的前景以及如何进阶 1.自动化测试的介绍&#xff1a; 自动化测试什么是&#xff0c;有哪些被称作自动化测试&#xf…

2024-03-20 作业

作业要求&#xff1a; 1> 创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;、姓名、年龄、薪资。 2> 添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff09; 3> 修改某一个工人的薪资&#xff08;确定的一个&#x…

C++利用开散列哈希表封装unordered_set,unordered_map

C利用开散列哈希表封装unordered_set,unordered_map 一.前言1.开散列的哈希表完整代码 二.模板参数1.HashNode的改造2.封装unordered_set和unordered_map的第一步1.unordered_set2.unordered_map 3.HashTable 三.string的哈希函数的模板特化四.迭代器类1.operator运算符重载1.动…

算法系列--递归

一.如何理解递归 递归对于初学者来说是一个非常抽象的概念,笔者在第一次学习时也是迷迷糊糊的(二叉树遍历),递归的代码看起来非常的简洁,优美,但是如何想出来递归的思路或者为什么能用递归这是初学者很难分析出来的 笔者在学习的过程中通过刷题,也总结出自己的一些经验,总结来…

Beamer模板——基于LaTeX制作学术PPT

Beamer模板——基于LaTeX制作学术PPT 介绍Beamer的基本使用安装和编译用于学术汇报的模板项目代码模板效果图 Beamer的高级特性动态效果分栏布局定理环境 介绍 在学术领域&#xff0c;演示文稿是展示和讨论研究成果的重要方式。传统的PowerPoint虽然方便&#xff0c;但在处理复…

基于python+vue家政服务系统flask-django-php-nodejs

相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低家政公司的运营人员成本&#xff0c;实现了家政服务的标准化、制度化、程序化的管理&#xff0c;有效地防止了家政服务的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准确地…

MAC本安装telnet

Linux运维工具-ywtool 目录 1.打开终端1.先安装brew命令2.写入环境变量4.安装telnet 1.打开终端 访达 - 应用程序(左侧) - 实用工具(右侧) - 终端 #注意:登入终端用普通用户,不要用MAC的root用户1.先安装brew命令 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/H…

什么是高防CDN?

高防CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;在网络安全中的作用非常重要。它通过一种特别的方式来保护网站和网络应用程序免受大规模DDoS攻击。以下是它的一些主要优势&#xff1a; 01 分布式防护 高防CDN通过在全球各地设立大量的节点…

智能电表多少钱一个?

嗨&#xff0c;朋友们&#xff0c;你是否好奇过家里那个默默工作的智能电表到底值多少钱呢?今天我们就来聊聊这个话题&#xff0c;一起走进智能电表的世界&#xff0c;看看它们是如何从传统的机械表进化为现代的智能设备&#xff0c;并了解它们的价格区间。 首先&#xff0c;…

基于Java+SpringBoot+vue+element实现毕业就业招聘系统

基于JavaSpringBootvueelement实现毕业就业招聘系统 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取…

宏宇、萨米特、新明珠、金意陶、简一、科达、力泰、道氏、SITI BT、POPPI……35家参展商发布亮点

3月18日&#xff0c;2024佛山潭洲陶瓷展&#xff08;4月18-22日&#xff09;亮点发布会在广东新媒体产业园成功举办&#xff0c;主题为“我们不一样”。 陶城报社社长、佛山潭洲陶瓷展总经理李新良代表主办方&#xff0c;发布了2024佛山潭洲陶瓷展的“不一样”&#xff1b;佛山…

位运算第三弹

力扣268.丢失的数字 public static int missingNumber(int[] nums) {int nnums.length;int []retnew int[n1];for(int i1;i<n;i){ret[nums[i-1]];}for(int i0;i<n;i){if(ret[i]0){return i;}}return 0;} 和上一道题&#xff0c;一个性质&#xff0c;用的是底层哈希表的思…

C语言例:表达式10<<3+1的值

10的二进制 00001010 10<<3 01010000 十制左移m位&#xff0c;乘以。 0101 0000 十进制80 10<<31 81