身份证号码识别(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 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全栈开发社区查看