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

news2024/9/19 8:20:59

身份证号码识别(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 idcardocr

import (
	"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,a
			if 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,a
			if 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 = x
				x = 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 = y
				y = 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 + 1
				x = 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 + 1
				y = 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 = x
				x = 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 = y
				y = 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 + 1
				x = 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 + 1
				y = 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.Image
	leftX := 0
	for x := 0; x < src.Bounds().Dx(); x++ {
		temp := false
		for y := 0; y < src.Bounds().Dy(); y++ {
			r, _, _, _ := src.At(x, y).RGBA()
			if r == 0xFFFF {
				temp = true
				break
			}
		}
		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 := false
			for y := 0; y < src.Bounds().Dy(); y++ {
				r, _, _, _ := src.At(x1, y).RGBA()
				if r == 0xFFFF {
					temp = true
					break
				}
			}
			if temp {
				leftX = x1
				x = x1
				break
			}
		}
		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.0
		for k, v := range Data {
			sum := 0
			for i := 0; i < 64; i++ {
				if v[i:i+1] == sign[i:i+1] {
					sum++
				}
			}
			//不断比较当匹配率达到最大时,就是此时所对应的数字
			if float64(sum)/64 > percent {
				number = k
				percent = float64(sum) / 64
			}
		}

		id += number
	}
	return id
}

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

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

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

相关文章

ARM 工业边缘计算机与 C# 编程的完美融合

在工业领域&#xff0c;随着智能化和数字化的不断推进&#xff0c;ARM 工业边缘计算机凭借其出色的性能和低功耗等优势&#xff0c;逐渐成为众多应用场景的重要支撑。而 C# 编程语言的强大功能和广泛适用性&#xff0c;使其在与 ARM 工业边缘计算机的结合中展现出了巨大的潜力。…

Python | Leetcode Python题解之第415题字符串相加

题目&#xff1a; 题解&#xff1a; class Solution:def addStrings(self, num1: str, num2: str) -> str:res ""i, j, carry len(num1) - 1, len(num2) - 1, 0while i > 0 or j > 0:n1 int(num1[i]) if i > 0 else 0n2 int(num2[j]) if j > 0 e…

C++ -命名空间-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C -命名空间-详解 1.C语言缺点之一 -- 命名冲突2.命名空间2.1定义2.2使用访问命名空间中的变量展开命名空间域指定访问命名空间域 2.3其他功能 3.C 标准库中的命名空间指定展开…

华为OD机试 - 二维伞的雨滴效应(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

徒增成本,还是有备无患?说说4G模组SIM双卡切换

初学开发的小伙伴提出疑问&#xff1a; 手机双卡可以理解&#xff0c;人情世故各种缘由…… 物联网设备有必要双卡吗&#xff0c;会不会太浪费&#xff1f; 实际应用中&#xff0c;双卡可不是徒增成本的摆设&#xff0c;而是有备无患的必需。 在使用4G模组双卡功能的场景下&a…

2024年数学建模比赛题目及解题代码

目录 一、引言 1. 1竞赛背景介绍 1.1.1数学建模竞赛概述 1.1.2生产过程决策问题在竞赛中的重要性 1.2 解题前准备 1.2.2 工具与资源准备 1.2.3 心态调整与策略规划 二、问题理解与分析 三、模型构建与求解 3.1 模型选择与设计 3.1.1 根据问题特性选择合适的数学模型类…

每日学习一个数据结构-红黑树

文章目录 什么是红黑树&#xff1f;示意图红黑树的特点红黑树的节点结构插入和删除操作旋转操作重新着色 红黑树的应用 树的构造过程插入新节点自平衡调整策略 示例 查询过程 什么是红黑树&#xff1f; 红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉查找树…

关于嵌入式硬件需要了解的基础知识

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于嵌入式硬件基础知识的相关内容&#xff…

TCP: Textual-based Class-aware Prompt tuning for Visual-Language Model

文章汇总 存在的问题 原文&#xff1a;具有图像特定知识的图像条件提示符号在提升类嵌入分布方面的能力较差。 个人理解&#xff1a;单纯把"a photo of {class}"这种提示模版作为输入是不利于text encoder学习的 动机 在可学习的提示和每一类的文本知识之间建立…

2024短剧系统开发,付费短剧小程序app源码教程,分销功能讲解搭建上线

短剧系统技术栈 前端&#xff1a;vue3uniapp 后端&#xff1a; php 数据库&#xff1a;mysql 服务器环境&#xff1a; centos7.6 宝塔 php7.4 MySQL5.7 一、短剧系统功能 短剧用户端&#xff1a; 小程序、抖音小程序、快手小程序、APP、 z付宝小程序 系统用户端详细功能&…

Java创建教程!(*  ̄3)(ε ̄ *)

Java 构造函数 Java面向对象设计 - Java构造函数 构造函数是用于在对象创建后立即初始化对象的代码块。 构造函数的结构看起来类似于一个方法。 声明构造函数 构造函数声明的一般语法是 <Modifiers> <Constructor Name>(<parameters list>) throws <…

安卓14剖析SystemUI的ShadeLogger/LogBuffer日志动态控制输出dumpsy机制

背景&#xff1a; 看SystemUI的锁屏相关代码时候发现SystemUI有一个日志打印相关的方法调用&#xff0c;相比于常规的Log.i直接可以logcat查看方式还是比较新颖。 具体日志打印代码如下&#xff1a; 下面就来介绍一下这个ShadeLogger到底是如何打印的。 分析源码&#xff1…

scanf()函数的介绍及基础用法

目录 scanf&#xff08;&#xff09;函数的介绍及基础用法 一&#xff1a;头文件 二&#xff1a;一般用法 三&#xff1a;返回值 1. 正整数的情况&#xff1a; 2. 0 的情况&#xff1a; 3. EOF的情况&#xff1a; 四&#xff1a;说明 scanf&#xff08;&#xff09;函数…

CCF202006_1

问题描述 试题编号&#xff1a;202006-1试题名称&#xff1a;线性分类器时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 题解&#xff1a; #include<bits/stdc.h>using namespace std; int n, m;struct Node {int x, y;char ch; }node[1010…

9.16日常记录

1.LRU算法 核心思想:LRU算法&#xff08;Least Recently Used&#xff09;是一种常用的缓存淘汰策略&#xff0c;它的核心思想是“如果数据最近被访问过&#xff0c;那么将来被访问的几率也更高”。LRU算法主要用于内存管理和缓存系统。当内存或缓存空间已满&#xff0c;需要腾…

【工具变量】气候适应型试点城市DID(2005-2022年)

数据来源&#xff1a;本数据来源于中国ZF网发布的《关于深化气候适应型城市建设试点的通知》 时间跨度&#xff1a;2005-2022年数据简介&#xff1a;适应型试点城市是指在应对气候变化、提高城市适应能力方面进行先行先试的城市。根据中国ZF网发布的《关于深化气候适应型城市建…

在 Stable Diffusion 1.5 中 Lora, Dreambooth, Textual Inversion的详解指北

Lora, Dreambooth and Textual Inversion 说明 您是否想象过您可爱的宠物与埃菲尔铁塔合影的画面&#xff0c;或者想象过如何生成一张带有您朋友面孔的人工智能图像&#xff1f; 是的&#xff0c;通过稳定扩散技术的微调&#xff0c;这完全是可能的&#xff01; 创建这些场景…

NISP 一级 | 7.2 信息安全风险管理

关注这个证书的其他相关笔记&#xff1a;NISP 一级 —— 考证笔记合集-CSDN博客 0x01&#xff1a;信息安全风险 信息系统不可能达到绝对安全&#xff0c;但可以通过安全风险&#xff08;以下简称“风险”&#xff09;控制来实现符合个人或单位目标的一定程度的安全。信息安全管…

xxl-job、Quartz、power-job、elastic-job对比选型

一、框架对比 1. Quartz 优点&#xff1a;稳定性和可扩展性好&#xff0c;适用于企业级应用&#xff1b;调度功能丰富&#xff0c;满足多种需求。 缺点&#xff1a;本身不提供原生的分布式支持&#xff0c;需要通过扩展或与其他组件结合来实现分布式任务调度&#xff1b;调度…

树莓派驱动之spi回环测试

开启spi sudo raspi-config选择Interfacing options,选择spi打开 lsmod可以看到spi_bcm2835 短接MISO和MOSI 编写回环代码spitest.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h>…