身份证号码识别(golang)
使用golang的image库写的身份证号码识别,还有用了一个resize外部库,用来更改图片尺寸大小,将每个数字所在的图片的大小进行统一可以更好的进行数字识别,库名 :“github.com/nfnt/resize”
测试身份证图片
可用来测试是否识别成功,测试时拍照自己证件要清晰,并把多余空白裁剪掉。
基本思路
- 拿到一张身份证图片,先确定身份证号码的位置
- 将该部分取出,然后进行二值化,比如将数字变成白色,背景变为黑色
- 按照第二步数字的颜色(这里以数字为白色为例),遍历像素点找出左边第一个白点和右边最后一个白点的坐标,对图片更加细致的切割
- 将图片分割即将每一个数字切割出来
- 识别数字
- 基础调用代码实现如下:
// 打开图片
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全栈开发社区查看