Golang开发的OCR-身份证号码识别(不依赖第三方)

身份证号码识别(golang)

使用golang的image库写的身份证号码识别,还有用了一个resize外部库,用来更改图片尺寸大小,将每个数字所在的图片的大小进行统一可以更好的进行数字识别,库名 :“github.com/nfnt/resize”

测试身份证图片

可用来测试是否识别成功,测试时拍照自己证件要清晰,并把多余空白裁剪掉。

带有x号码

基本思路

  1. 拿到一张身份证图片,先确定身份证号码的位置
  2. 将该部分取出,然后进行二值化,比如将数字变成白色,背景变为黑色
  3. 按照第二步数字的颜色(这里以数字为白色为例),遍历像素点找出左边第一个白点和右边最后一个白点的坐标,对图片更加细致的切割
  4. 将图片分割即将每一个数字切割出来
  5. 识别数字
  • 基础调用代码实现如下:
	//  打开图片imgFile, err := os.Open("./image/idx.jpg")if err != nil {panic(fmt.Sprintf("打开文件失败:%+v", err))}defer imgFile.Close()// 解析图片img, err := jpeg.Decode(imgFile)if err != nil {panic(fmt.Sprintf("解析图片失败:%+v", err))}locImg := Number(img)binImg := Binarization(locImg)imgCutSide := CutImage(binImg)imgCutSilce := SplitImage(imgCutSide)imgNum := NumberDistinguish(imgCutSilce)fmt.Println("识别结果:", imgNum)fmt.Println("识别位数:", len(imgNum))
  • 封装组件代码实现

本插件增加了图片空白裁剪,裁出只含有内容部分再进行识别,调用代码如下: 

	//  打开图片imgFile, err := os.Open("./image/idx.jpg")if err != nil {panic(fmt.Sprintf("打开文件失败:%+v", err))}defer imgFile.Close()// 解析图片img, err := jpeg.Decode(imgFile)if err != nil {panic(fmt.Sprintf("解析图片失败:%+v", err))}//裁剪内容部分,去除空白binImg_c := BinarizationBak(img)imgCutSide_c := CutImage_C(binImg_c, img)//开始处理识别和上面的基础调用流程一致locImg := Number(imgCutSide_c)binImg := Binarization(locImg)imgCutSide := CutImage(binImg)imgCutSilce := SplitImage(imgCutSide)imgNum := NumberDistinguish(imgCutSilce)fmt.Println("识别结果:", imgNum)fmt.Println("识别位数:", len(imgNum))

在GoFly快速开框架使用

在使用插件时,安装后直接调plugin.IdCardNumber()即可,接口调用示例如下:

// 测试识别身份证接口
func (api *Test) GetIdCard(c *gf.GinCtx) {num, err := plugin.IdCardNumber("./utils/plugin/idcardocr/demoimg/idx.jpg")if err != nil {gf.Failed().SetMsg(err.Error()).Regin(c)return}gf.Success().SetMsg("识别身份证成功").SetData(num).Regin(c)
}

完整插件代码

如果你那不使用gofly框架直接使用下面完整代码,代码如下:

package idcardocrimport ("image""image/color""image/draw""github.com/nfnt/resize"
)// 1.号码定位-获取身份证号码所在位置图片-截出来
func Number(src image.Image) image.Image {rect := src.Bounds() // 获取图片的大小// fmt.Println("号码定位x", rect.Dx(), rect.Size())//左上角坐标//此处图片的尺寸需要根据所需识别的图片进行确定// leftold := image.Point{X: rect.Dx() * 220 / 620, Y: rect.Dy() * 325 / 385}left := image.Point{X: int(float64(rect.Dx()) * 0.31), Y: int(float64(rect.Dy()) * 0.78)}// fmt.Println("号码定位left", left)//右下角坐标//此处图片的尺寸需要根据所需识别的图片进行确定// right := image.Point{X: rect.Dx() * 540 / 620, Y: rect.Dy() * 345 / 385}right := image.Point{X: int(float64(rect.Dx()) * 0.96), Y: int(float64(rect.Dy()) * 0.95)}// fmt.Println("号码定位right", right)newReact := image.Rectangle{Min: image.Point{X: 0, Y: 0},Max: image.Point{X: right.X - left.X, Y: right.Y - left.Y + 10},} // 创建一个新的矩形 ,将原图切割后的图片保存在该矩形中newImage := image.NewRGBA(newReact)                 // 创建一个新的图片draw.Draw(newImage, newReact, src, left, draw.Over) // 将原图绘制到新图片中return newImage
}// 2.将图片二值化
func Binarization(src image.Image) image.Image {//将图片灰化dst := image.NewGray16(src.Bounds())                          // 创建一个新的灰度图draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src) // 将原图绘制到新图片中//遍历像素点,实现二值化for x := 0; x < src.Bounds().Dx(); x++ {for y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x, y).RGBA() //取出每个像素的r,g,b,aif r < 0x5555 {dst.Set(x, y, color.White) //将灰度值小于0x5555的像素置为0} else {dst.Set(x, y, color.Black)}}}return dst
}// 22.将图片二值化
func BinarizationBak(src image.Image) image.Image {//将图片灰化dst := image.NewGray16(src.Bounds())                          // 创建一个新的灰度图draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src) // 将原图绘制到新图片中//遍历像素点,实现二值化for x := 0; x < src.Bounds().Dx(); x++ {for y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x, y).RGBA() //取出每个像素的r,g,b,aif r >= 60535 {dst.Set(x, y, color.Black) //将灰度值小于0x5555的像素置为0} else {dst.Set(x, y, color.White)}}}return dst
}// 3.寻找边缘坐标更加细致的切割图片
func CutImage(src image.Image) image.Image {var left, right image.Point //左上角右下角坐标//寻找左边边缘白点的x坐标for x := 0; x < src.Bounds().Dx(); x++ {for y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {left.X = xx = src.Bounds().Dx() //使外层循环结束break}}}//寻找左边边缘白点的y坐标for y := 0; y < src.Bounds().Dy(); y++ {for x := 0; x < src.Bounds().Dx(); x++ {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {left.Y = yy = src.Bounds().Dy() //使外层循环结束break}}}//寻找右边边缘白点的x坐标for x := src.Bounds().Dx(); x > 0; x-- {for y := src.Bounds().Dy(); y > 0; y-- {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {right.X = x + 1x = 0 //使外层循环结束break}}}//寻找右边边缘白点的y坐标for y := src.Bounds().Dy() - 1; y > 0; y-- {for x := src.Bounds().Dx() - 1; x > 0; x-- {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {right.Y = y + 1y = 0 //使外层循环结束break}}}//按照坐标点将图像精准切割newReact := image.Rect(0, 0, right.X-left.X+1,right.Y-left.Y+2) // 创建一个新的矩形 ,将原图切割后的图片保存在该矩形中dst := image.NewRGBA(newReact)draw.Draw(dst, dst.Bounds(), src, left, draw.Over)return dst
}// 3.2.用来截取身份证部分-去除白色背景内容
func CutImage_C(src, oldsrc image.Image) image.Image {var left, right image.Point //左上角右下角坐标//寻找左边边缘白点的x坐标for x := 0; x < src.Bounds().Dx(); x++ {for y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {left.X = xx = src.Bounds().Dx() //使外层循环结束break}}}//寻找左边边缘白点的y坐标for y := 0; y < src.Bounds().Dy(); y++ {for x := 0; x < src.Bounds().Dx(); x++ {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {left.Y = yy = src.Bounds().Dy() //使外层循环结束break}}}//寻找右边边缘白点的x坐标for x := src.Bounds().Dx(); x > 0; x-- {for y := src.Bounds().Dy(); y > 0; y-- {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {right.X = x + 1x = 0 //使外层循环结束break}}}//寻找右边边缘白点的y坐标for y := src.Bounds().Dy() - 1; y > 0; y-- {for x := src.Bounds().Dx() - 1; x > 0; x-- {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {right.Y = y + 1y = 0 //使外层循环结束break}}}//按照坐标点将图像精准切割newReact := image.Rect(0, 0, right.X-left.X, right.Y-left.Y) // 创建一个新的矩形 ,将原图切割后的图片保存在该矩形中dst := image.NewRGBA(newReact)draw.Draw(dst, dst.Bounds(), oldsrc, left, draw.Over)return dst
}// 4.将每一个数字切割出来
func SplitImage(src image.Image) []image.Image {var dsts []image.ImageleftX := 0for x := 0; x < src.Bounds().Dx(); x++ {temp := falsefor y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x, y).RGBA()if r == 0xFFFF {temp = truebreak}}if temp {continue}dst := image.NewGray16(image.Rect(0, 0, x-leftX, src.Bounds().Dy()))draw.Draw(dst, dst.Bounds(), src, image.Point{X: leftX, Y: 0}, draw.Src)//下一个起点for x1 := x + 1; x1 < src.Bounds().Dx(); x1++ {temp := falsefor y := 0; y < src.Bounds().Dy(); y++ {r, _, _, _ := src.At(x1, y).RGBA()if r == 0xFFFF {temp = truebreak}}if temp {leftX = x1x = x1break}}img := resize.Resize(8, 8, dst, resize.Lanczos3)dsts = append(dsts, img)}//fmt.Println(len(dsts))return dsts
}// 4.图片识别 将图片转换为01的数据 进行验证
func NumberDistinguish(srcs []image.Image) string {// 指纹验证var Data = map[string]string{"0": "0111110011111110000000001000000010000000100000100111111000011000","1": "0100000001000000010000001100000011000000111111101111111011111110","2": "0000001010000110000010001000100010011000000100001110000001100000","3": "0000000000000000000000000001000000110000011100101101001011001110","4": "0000110000011100001001000100010000000100000111100000110000000100","5": "0000000011100000001000000000000000000010000100000001011000011100","6": "0000110000111110001100100110000010000000100100100001111000001100","7": "0000000000000000000011100001111000010000001000001100000011000000","8": "0100111011111010100100100001000000010000101100100110111000000100","9": "0010000001110000100110000000101000001110100011000111100001100000","X": "0100001001100110001111000001100000111000011111000100011000000010",}id := ""for i := 0; i < len(srcs); i++ {// 获取图片的指纹sign := ""for x := 0; x < srcs[i].Bounds().Dx(); x++ {for y := 0; y < srcs[i].Bounds().Dy(); y++ {r, _, _, _ := srcs[i].At(x, y).RGBA()if r > 0x7777 {sign += "1"} else {sign += "0"}}}// 对比指纹number := ""//对比相似率percent := 0.0for k, v := range Data {sum := 0for i := 0; i < 64; i++ {if v[i:i+1] == sign[i:i+1] {sum++}}//不断比较当匹配率达到最大时,就是此时所对应的数字if float64(sum)/64 > percent {number = kpercent = float64(sum) / 64}}id += number}return id
}

更多Go插件封装可以到Gofly全栈开发社区查看

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

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

相关文章

上海我店平台 8月新增注册用户89w 两年破百亿销售额!

近年来&#xff0c;网络空间内涌现了一个备受瞩目的新平台——“上海我店”&#xff0c;其公布的业绩数据显示&#xff0c;短短三年内&#xff0c;该平台交易流水已突破百亿大关&#xff0c;上月更是迎来了近百万的新增注册用户&#xff0c;这一消息迅速吸引了众多商家的目光。…

系统架构设计师 需求分析篇一

&#x1f4d8; 结构化分析SA 思想 自顶向下&#xff1a;像剥洋葱一样&#xff0c;层层深入&#xff0c;大问题拆成小问题&#xff0c;再拆成更小的问题。 核心模型 数据字典 &#x1f4d4;&#xff1a;记录数据元素的点点滴滴&#xff0c;从属性到使用方式&#xff0c;无所…

Java基础面试题——异常

目录 关系图 1. Throwable和Exception之间的关系 2.异常分为哪两大类 3.常见的 RuntimeException 4. 常见的 Error 5.什么是已检查异常和未检查异常&#xff1f;它们的区别是什么&#xff1f; 6.Java 中如何自定义异常&#xff1f; 7.throw 和 throws 的区别是什么&…

简述混沌神经网络

混沌神经网络是一种结合了神经网络与混沌理论的新型智能信息处理系统。以下是对混沌神经网络的详细解析&#xff1a; 一、定义与背景 混沌神经网络是由于神经网络具有高度非线性动力学系统的特性&#xff0c;而混沌又具有无规则性、遍历性、随机性等特点&#xff0c;因此神经网…

端侧 AI 的新突破:面壁智能 MiniCPM 3.0

在人工智能领域&#xff0c;每一次技术的革新都可能引发一场小小的革命。 ChatGPT-3.5 曾经凭借其惊人的表现赢得了大众的关注&#xff0c;但如今&#xff0c;随着国内AI公司面壁智能推出的新端侧基座模型&#xff0c;人们开始重新审视端侧AI的潜力和未来。 这款名为MiniCPM …

linux----进程地址空间

前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、空间分布 二、栈和堆的特点 &#xff08;1&#xff09;栈堆相对而生&#xff0c;堆是向上增长的&#xff0c;栈是向下增长的。 验证&#xff1a;堆是向上增长的 这里我们看到申请的堆&#xff…

Ubuntu 20.04安装pycharm2022及配置快捷方式

一、下载与安装 1. 下载 在 官网 下载所需版本&#xff0c;如&#xff1a;下载 2022.3.3 - Linux (tar.gz) 2. 安装 设置自定义安装路径(推荐在 /opt/ 路径下)并安装 mkdir -p ~/Documents/software/pycharm/ cd ~/Documents/software/pycharm/ mv ~/Downloads/pycharm-c…

解决 TortoiseGitPlink Fatal Error:深入解析

解决 TortoiseGitPlink Fatal Error&#xff1a;深入解析 在 Windows 平台上&#xff0c;开发者使用 Git 和 TortoiseGit 进行版本控制时&#xff0c;有时会遇到 TortoiseGitPlink Fatal Error。该错误通常是在推送/拉取代码时&#xff0c;客户端未能提供正确的 SSH 密钥。 1…

单相电多相电

目录 1. 单相电 2. 多相电 3. 其他多相电系统 单相电和多相电是电力系统中常见的两种供电方式&#xff0c;主要区别在于电力传输的相数。以下分别介绍它们的基本概念、特征、以及应用场景。 1. 单相电 定义&#xff1a; 单相电指的是只有一根火线和一根零线的电力系统。这…

企业微信应用消息收发实施记录

一、前置配置 1.1 进入我的企业页面&#xff0c;记录下企业ID。 1.2 创建企微应用&#xff0c;记录下应用的 AgentId 和 Secret。 1.3 设置应用的企业可信IP&#xff0c;将服务器公网 IP 填入即可。 1.4 设置应用接收消息API 填入服务器 API 地址&#xff0c;并记录下随机获取…

JAVA精准匹配同城找搭子交友系统小程序源码

精准匹配&#xff0c;同城找搭子交友系统 &#x1f50d; 开篇&#xff1a;告别盲目&#xff0c;迎接精准交友新时代 在这个快节奏的城市生活中&#xff0c;你是否厌倦了无效的社交和孤独的夜晚&#xff1f;是时候告别那些盲目的交友尝试&#xff0c;迎接“精准匹配同城找搭子…

SpringBoot启动横幅输出到控制台。

在Spring Boot应用中&#xff0c;banner.txt 文件通常放置在项目的资源目录下&#xff08;通常是 src/main/resources&#xff09;&#xff0c;当Spring Boot应用启动时&#xff0c;会自动读取这个文件并将其内容作为启动横幅输出到控制台。这可以用来个性化你的应用程序启动时…

docker|Oracle数据库|docker快速部署Oracle11g和数据库的持久化(可用于生产环境)

一、 容器数据持久化的概念 docker做为容器化的领先技术&#xff0c;现在广泛应用于各个平台中&#xff0c;但不知道什么时候有一个说法是docker并不适用容器化数据库&#xff0c;说容器化的数据库性能不稳定&#xff0c;其实&#xff0c;这个说法主要是因为对docker的数据持…

路径处理 | 关键点提取之Douglas–Peucker算法(附ROS C++/Python实现)

目录 0 专栏介绍1 路径关键点提取2 道格拉斯-普克算法Douglas–Peucker3 算法实现与可视化3.1 ROS C仿真3.2 Python仿真 0 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战&#xff0c;包括&#xff1a;…

MateBook 16s 2023在Deepin下开启性能模式,调节风扇转速到最大,全网首发!

方法 在Deepin下按住Fnp快捷键&#xff0c;开启性能模式。 验证 首先去debian下载acpi-call-dkms https://packages.debian.org/sid/all/acpi-call-dkms/download 然后使用root用户执行&#xff1a; apt install --simulate ./acpi-call-dkms_1.2.2-2.1_all.deb apt inst…

数据结构(7.3_4)——红黑树的定义和性质

红黑树和平衡排序二叉树的查插删时间 平衡二叉树的适用场景&#xff1a;适用以查为主、很少插入/删除vd场景 红黑树&#xff1a;适用于频繁插入、删除的场景&#xff0c;实用性更强 红黑树的考点 红黑树的定义&#xff1a; 红黑树的二叉排序树&#xff1a;左子树结点值<…

Day04_JVM实战

文章目录 一、gc日志和dump快照GC日志是什么,要怎么看?dump快照是什么?要怎么看?二、gc日志和dump快照实战java.lang.OutOfMemoryError:Java heap space1、gc.log怎么看2、heapdump.hprof怎么看?①jvisualvm查看②使用MAT查看java.lang.OutOfMemoryError:Metaspace1、实时…

hive-拉链表

目录 拉链表概述缓慢变化维拉链表定义 拉链表的实现常规拉链表历史数据每日新增数据历史数据与新增数据的合并 分区拉链表 拉链表概述 缓慢变化维 通常我们用一张维度表来维护维度信息&#xff0c;比如用户手机号码信息。然而随着时间的变化&#xff0c;某些用户信息会发生改…

[OPEN SQL] SELECT语句

本次操作使用的数据库表为SCUSTOM&#xff0c;其字段内容如下所示 航班用户(SCUSTOM) 1.SELECT语句 SELECT语句从数据库表中读取必要的数据 1.1 读取一行数据 语法格式 SELECT SINGLE <cols>... WHERE cols&#xff1a;数据库表的字段 从数据库表中读取一条数据可使…

ETLCloud:新一代ETL数据抽取工具的定义与革新

数据集成、数据治理已经成为推动企业数字化转型的核心动力&#xff0c;现在的企业比任何时候都需要一个更为强大的新一代数据集成工具来处理、整合并转化多种数据源。 而ETL&#xff08;数据提取、转换、加载&#xff09;作为数据管理的关键步骤&#xff0c;已在企业数据架构中…