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.xdnf.cn/news/143890.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 ——— 编写函数,判断一个整数是否是回文整数

目录 题目要求 代码实现 题目要求 编写一个函数&#xff0c;用来判断一个整数是否是回文整数&#xff0c;如果是回文整数就返回 true &#xff0c;如果不是就返回 false 举例说明&#xff1a; 输入&#xff1a;121 输出&#xff1a;true 输入&#xff1a;1321 输出&#xf…

怎么把文件生成二维码活码?支持生成多种格式文件的二维码教程

怎么把文件做成二维码分享给其他人预览或下载呢&#xff1f;现在使用二维码来展示或者分享文件的使用场景越来越多&#xff0c;这种方式可以帮助其他人更快的获取文件内容&#xff0c;有利于提升文件传输的效率。二维码可以长期存储文件&#xff0c;获取文件会更加的灵活方便&a…

翻转对00

题目链接 翻转对 题目描述 注意点 给定数组的长度不会超过50000输入数组中的所有数字都在32位整数的表示范围内 解答思路 本题与区间和的个数类似&#xff0c;都是使用归并排序统计满足题意的数量&#xff0c;归并排序后可以有效减少比较的数量归并排序的思路为&#xff1…

心觉:成功学就像一把刀,有什么作用关键在于使用者(一)

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作173/1000天 很多人觉得成功学是鸡汤&#xff0c;是没用的&#xff0c;甚至是骗人的 我先保持中立&#xff0c;不知道对不对 我们先…

【Python报错已解决】AttributeError: ‘WindowsPath‘ object has no attribute ‘rstrip‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

程序员的宝藏,七大常用Python库!

在Python的广泛应用中&#xff0c;七大常用库扮演着至关重要的角色。这些库覆盖了数据分析、机器学习、科学计算等多个领域&#xff0c;为开发者提供了强大的工具集。以下是这七大常用Python库的详细介绍及其优缺点&#xff1a; 1. NumPy 详细介绍&#xff1a; NumPy是Python的…

在Ubuntu使用VScode配合GDB完成代码调试

想学一下Ubuntu下的vscode代码调试&#xff0c;在网上找了很多博客&#xff0c;发现根本不管用&#xff0c;而且很多都是在Windows下的&#xff0c;与我的需求&#xff08;使用CMakeLists.txt&#xff09;不同&#xff0c;根本不能用&#xff0c;研究了一下。并记录。 1.创建C…

浅谈人工智能之Java调用基于Ollama本地大模型

引言 随着人工智能技术的飞速发展&#xff0c;大型语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为自然语言处理领域的研究热点。Ollama是一个强大的工具&#xff0c;它使得在本地部署和管理这些大型语言模型变得更加便捷。本文档旨在指导Java开发者如何在…

【C++ Primer Plus习题】16.7

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> #include <vector> #include <…

I/O流(Java)

目录 1. IO概述 1.1 什么是IO 1.2 IO的分类 1.3 IO的流向说明图解 1.4 顶级父类 2. File类 2.1 概述 2.2 构造方法 2.3 常用方法 2.3.1 获取功能的方法 2.3.2 绝对路径和相对路径 2.3.3 判断功能的方法 2.3.4 创建删除功能的方法 2.3.5 目录的遍历 3. 字节流 3…

[Golang] Context

[Golang] Context 文章目录 [Golang] Context什么是context创建context创建根context创建context context的作用并发控制context.WithCancelcontext.WithDeadlinecontext.WithTimeoutcontext.WithValue 什么是context Golang在1.7版本中引入了一个标准库的接口context&#xf…

计算机毕业设计 办公用品管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

如何将扫码提交的数据直接推送到企业微信、钉钉、飞书群聊?详细教程

功能介绍 在草料制作的表单中&#xff0c;填表人扫码填写并提交数据后&#xff0c;这些信息可以立即通过企业微信、钉钉或飞书自动推送到相应的群聊中&#xff0c;实现即时共享和沟通&#xff0c;提升团队协作效率。 设置教程 企业微信 钉钉 飞书

蚂蚁在 RAG 与向量检索上的实践:技术应用与创新分析

引言 在AI技术迅猛发展的背景下&#xff0c;如何有效地处理海量数据成为了技术创新的关键问题。向量数据库和RAG&#xff08;Retrieval-Augmented Generation&#xff09;技术结合&#xff0c;为提升生成式AI应用的准确性和实时性提供了有效的解决方案。本文结合蚂蚁集团在向量…

国外创意二维码应用案例:韩国Cheil特别制作“希望胶带”,帮助寻找失踪儿童!

每年&#xff0c;在全世界都有大量的儿童失踪案件发生。对于父母来说&#xff0c;仅凭一张照片、一张海报要在茫茫人海里找到失踪的孩子&#xff0c;何其艰难&#xff1f; 2020年5月&#xff0c;韩国广告公司Cheil与韩国国家警察局宣布&#xff1a;为寻找长期失踪儿童&#xf…

9.18作业

提示并输入一个字符串&#xff0c;统计该字符串中字母、数字、空格、其他字符的个数并输出 代码展示 #include <iostream>using namespace std;int main() {string str;int countc 0; // 字母计数int countn 0; // 数字计数int count 0; // 空格计数int counto 0;…

面了智谱大模型算法岗,效率贼高!

最近这一两周不少互联网公司都已经开始秋招提前批面试了。不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解…

Renesas R7FA8D1BH (Cortex®-M85)内部RTC的应用

目录 概述 1 软硬件 1.1 软硬件环境信息 1.2 开发板信息 1.3 调试器信息 2 FSP配置RTC 2.1 配置参数 2.2 RTC模块介绍 3 RTC相关函数 3.1 R_RTC_Open() 3.2 R_RTC_Close() 3.3 R_RTC_ClockSourceSet() 3.4 R_RTC_CalendarTimeSet() 3.5 R_RTC_CalendarTimeGet()…

workbench的使用

connection name 是可以任意取的 Hostname 是数据库的地址&#xff0c;本地的话就默认是127.0.0.1 port是端口 选择store in value来存储密码 点击测试连接test connection 单击就可以登录&#xff0c;如果需要编辑的话&#xff0c;右键选择edit connection 可以选择删除账…

C++_类和对象(下篇)—— 内部类、匿名对象、对象拷贝时的编译器优化

目录 四、类和对象&#xff08;下篇&#xff09; 5、内部类 6、匿名对象 7、对象拷贝时的编译器优化 四、类和对象&#xff08;下篇&#xff09; 5、内部类 如果⼀个类定义在另⼀个类的内部&#xff0c;这个内部类就叫做内部类。内部类是⼀个独立的类&#xff0c;跟定义…