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

身份证号码识别(golang)

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

测试身份证图片

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


带有x号码

基本思路

  1. 拿到一张身份证图片,先确定身份证号码的位置
  2. 将该部分取出,然后进行二值化,比如将数字变成白色,背景变为黑色
  3. 按照第二步数字的颜色(这里以数字为白色为例),遍历像素点找出左边第一个白点和右边最后一个白点的坐标,对图片更加细致的切割
  4. 将图片分割即将每一个数字切割出来
  5. 识别数字
  • 基础调用代码实现如下:
Go 复制代码
	//  打开图片
	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))
  • 封装组件代码实现

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

Go 复制代码
	//  打开图片
	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()即可,接口调用示例如下:

Go 复制代码
// 测试识别身份证接口
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框架直接使用下面完整代码,代码如下:

Go 复制代码
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全栈开发社区查看

相关推荐
运维&陈同学3 分钟前
【Elasticsearch05】企业级日志分析系统ELK之集群工作原理
运维·开发语言·后端·python·elasticsearch·自动化·jenkins·哈希算法
ZVAyIVqt0UFji3 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
loop lee3 小时前
Nginx - 负载均衡及其配置(Balance)
java·开发语言·github
SomeB1oody3 小时前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
toto4123 小时前
线程安全与线程不安全
java·开发语言·安全
水木流年追梦4 小时前
【python因果库实战10】为何需要因果分析
开发语言·python
w(゚Д゚)w吓洗宝宝了5 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
AI人H哥会Java5 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
开心工作室_kaic6 小时前
springboot461学生成绩分析和弱项辅助系统设计(论文+源码)_kaic
开发语言·数据库·vue.js·php·apache
觉醒的程序猿6 小时前
vue2设置拖拽选中时间区域
开发语言·前端·javascript