![4479e9a79da87193760114b8a7832544.png](https://i-blog.csdnimg.cn/blog_migrate/2e8bb2b573c8efbdb28458713ff75bf7.jpeg)
首先做一下广告:
多年以前,闲得淡疼,曾经用 PHP 实现了一个从身份证照片识别身份证号码的程序。读/写 jpg 图片使用了 PHP 的 GD 库,其余全部手写 PHP 。在整个识别过程中有这样几个问题:
- 照片中身份证在什么位置?如何提取出来?
- 身份证号码在证上的什么位置?如何提取出来?
- 如何将号码切分成(18 个)数字和 x ?(身份证最后一位校验位有可能是 x 。)
- 如何识别 10 个数字和 x ?
本文分别将我采用的解决办法陈述一下。本文代码 github:
https://github.com/zhangjuefei/php_id_card_numbergithub.com1、照片中身份证在什么位置?如何提取出来?
例如下图的身份证(为保护隐私,以下示例图片都将部分身份信息涂灰):
![eae894348548bdb128076df746d37e83.png](https://i-blog.csdnimg.cn/blog_migrate/bb796daea7c82f6be4b4872c25f90eae.jpeg)
要想将身份证部分切割出来,要先找到身份证上、下、左、右四个边缘。我用 Sobel 算子检测图像中的“边缘”,得到结果如下:
![0da05285188b2e9e372da2ecb7e0e7c0.png](https://i-blog.csdnimg.cn/blog_migrate/2c6662be84a677ba9fc6553f0a5ca70c.jpeg)
这里存在一个不健壮之处:就是需要背景和身份证本身颜色尽量差别大些,这样才能检测出明显的边界,否则在这一步有可能失败。可以看到例图的边界比较清晰,因为我拍照时将身份证放在黑色哑光的椅子上。
接下来需要定位身份证的上、下、左、右四边缘。我使用了 Hough 变换来检测图像中的直线。由于实现较简单,这里存在另一个不鲁棒之处:如果图中除了身份证边缘之外还存在明显且长的直线,则会对边缘的识别形成干扰。
本来,用 Hough 变换可以得到四个边缘的方程,继而确定四角坐标。再通过映射变换,可以把扭歪放置拍照的身份证“扶正”。但为简单起见,我退而求其次:我要求身份证拍照时平放桌上、端正拍照。也就是说,要求身份证的上、下边界基本水平;左、右边界基本竖直。照得过于歪斜就有可能导致识别失败。通过 Hough 变换找到身份证的边框如下:
![a715d781b8145d9b1de96aae8b176a74.png](https://i-blog.csdnimg.cn/blog_migrate/68cf8f742dd63af0e2461302ae39194a.jpeg)
接下来只要根据这四条边缘将身份证切割出来即可,得到:
![54a627abb255601a96c41d2bef288f58.png](https://i-blog.csdnimg.cn/blog_migrate/b95217a9e674d16b03ef031da445734a.png)
至此,第一个问题解决(虽然妥协甚多,但是仍可改进),接下来是第二个问题。
2、身份证号码在证上的什么位置?如何提取出来?
这个问题要简单一些,我默认身份证号码在证上的位置是固定的,于是只是要按照定死的位置和长宽比例把号码部分切割出来即可,得到:
![7839925ec5df327634f8c9789dadec4c.png](https://i-blog.csdnimg.cn/blog_migrate/98a83278780e47e6c44ec72218520f17.jpeg)
但是这里也有坑。我见过有来自内蒙古自治区的身份证,汉字的旁边还有蒙文,导致号码不在通常位置上。这个问题我选择忽视。可以看到为保险起见,切割时在号码四周保留了比较宽的边缘。进入下一步——分割数字之前,需要将图像二值化并将周围的边缘去掉。
这里使用了一个土办法:首先根据 RGB 值的方差和均值进行二值化。这样背景的白色和红/蓝的纹路就都可以去掉了。如下:
![9c84a769eb17c7a31a5b8a77b4d4c649.png](https://i-blog.csdnimg.cn/blog_migrate/184f4222e49879cbb96bab6d6994c959.jpeg)
注意这个过程中的阈值都是写死的。如果要适应更广泛的情况,应该根据图片统计特性动态设置阈值。我为了简单,妥协之。
之后就是要去掉这串数字周围的白边。扫描上图的行和列,如果该行/列的前景色(黑色)像素所占比例低于阈值,则认为这行/列属于空白边缘。这里的阈值又是写死的。该过程的成果:
![e6c6d27ef2dc9a9bdf19f2e947d733b0.png](https://i-blog.csdnimg.cn/blog_migrate/bff46d7f9fd66430ef64fc4878c938d2.jpeg)
3、如何将号码切分成数字和 'x' ?
我使用了一个简单的切分方法:扫描图像的每一列,计数该列中前景色(黑色)的像素数量。高于阈值(又是写死的阈值)的列认为属于数字部分,反之认为属于数字间空隙。上述号码串图像的每列前景色(黑色)像素数如下图:
![9c5b23cfb069e30c5c2eb34d048c053a.png](https://i-blog.csdnimg.cn/blog_migrate/f1371a7743482f49efa7a534131b1b00.png)
上图中的每一簇(峰)就是一个数字。比如第三个有两个尖的簇就是数字 0 。两个尖就是 0 的两侧。又窄又高的那四簇就是 4 个 1 。能看出前几个数字是 110108 。后面几位我还是隐去了,因为会看的能看出是什么数字,保护隐私。
前景色(黑色)像素数低于阈值的部分就是数字间的空隙。这样就可以把这一串数字切分成一个个单独的数字图片。每一个数字图片采用之前用过的方法去除空白边缘。然后再用映射的办法缩放成 35 x 50 像素大小的图片。截取出来一个个数字图片如下:
![a65b190bc75fcf8ae75291a0253e2f05.png](https://i-blog.csdnimg.cn/blog_migrate/676e08a1ad98fd7542eaf814d8314370.jpeg)
![57e96e71550e6f3b57e9f1d7947c14b9.png](https://i-blog.csdnimg.cn/blog_migrate/f68245f9acc730ce4bd812997257ef04.jpeg)
![8521c45824426b8ab2ccd02c83378f14.png](https://i-blog.csdnimg.cn/blog_migrate/bc474bce02238cdca88b847e55ffb767.jpeg)
![09cceb104ea10734e91895c809f0fd65.png](https://i-blog.csdnimg.cn/blog_migrate/f3b247ee680ee5980853526988056542.jpeg)
![11d131154eeed4110312185b90bbeb03.png](https://i-blog.csdnimg.cn/blog_migrate/f8bebac1793085268023812fa943590d.jpeg)
![45fb129fae5f4014477fc00276c949f0.png](https://i-blog.csdnimg.cn/blog_migrate/0d35191c82efdb12b6669d9c236550c8.jpeg)
![7242586f47a342c1f9e13132b09abf21.png](https://i-blog.csdnimg.cn/blog_migrate/a9a82348bc1b28660752e800929342da.jpeg)
![5c3f128b997c488ef40b88d4696ae1fb.png](https://i-blog.csdnimg.cn/blog_migrate/81d310b1b243794b28f0b03c318447a6.jpeg)
![ad633d83d1dd25106012ebfc9cba70e4.png](https://i-blog.csdnimg.cn/blog_migrate/e5d904d1b0c796a34e8c47742a282be0.jpeg)
![d3747c73024e1002427e2a5836f0c843.png](https://i-blog.csdnimg.cn/blog_migrate/5b118a502d007c0e14a891b478a40cd2.jpeg)
我搜集了一些身份证照片,用上述方法取得了若干 0~9 数字和 x 的图片,作为训练集。需要指出,0~9 和 x 在身份证号中分布不均匀。有些较多,有些较少。我所能得到的身份证照片数量有限,不允许我抛弃样本以使 11 个总体数量均一。我忽略了这个问题。
4、如何识别 10 个数字和 'x' ?
至此,训练样本和待识别数字都被整理成 35x50 的二值图片。下一个问题是如何建模数字。我先将图片纵向分成 35x5 大小的 10 块,计数每一块中前景色(黑色)像素的个数,得到 10 个数值。再将图片横向分成 7x50 大小的 5 块,计数每一块中前景色(黑色)像素的个数,得到 5 个数值。这 15 个数值组成一个 15 维向量,作为该数字图像的建模。
把训练图片集中的数字图像建模,得到了来自 11 个总体(0~9 和 x)的一些训练样本。用均值估计每一个总体的期望。用加权平均(pooled)的方法估计了 11 个总体的共同协方差。这里我做了一个不太站得住脚的假设:就是 11 个总体的协方差矩阵相等。
识别时,把待识别的数字图像建模成 15 维向量,依次计算该向量与 11 个均值之间的Mahalanobis 距离:
取距离最小的均值所属的总体所代表的数字作为识别结果。
5、代码
大家可以百度图片几张身份证(挑选背景颜色单纯、与身份证颜色反差大且拍照端正的)测试一下,正确率还不错。以下是源代码:
test.php
<?php
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'recog.php');
ini_set('memory_limit', '2048M');
if ($argc < 2) {
echo "usage: php test.php id_card_file_path";
exit(1);
}
$recog = new recog($argv[1]);
try {
echo $recog->recognize_id_number() . "n";
} catch (recog_exception $e) {
echo "ERROR: " . $e->getMessage() . "n";
}
执行命令以及运行结果:
php test.php D:documentsstudy