这个坑是在工作中挖的,项目中突然有一个身份证识别的需求,主要功能是用户上传自己的身份证,我们提取身份证中的姓名,id等等做一个记录。虽说这个现在已经交给后端处理了,但还是很好奇啊有木有!感觉是个很吊的功能有木有~这种识别还有自己的统称OCR,厉害厉害推眼镜

实现信息识别的几种方式

查了半天资料,前端后端倒是都有插件可以引用,前端有一个ocrad.js,借助一些图像蜕化的工具便可提高成功率!
不过识别身份证的效果还是差强人意,可以说它也就能识别个纯白底纯黑字清晰度还要比较高的图片里的文字崩溃

在某探讨里看到某大神说到百度的文字识别服务,官网上就能试一试,赶紧把自己搞到那张模糊的身份证照片上传上去试了下(现在搞张能做实验的身份证照片也不容易啊!戳刀

身份证识别测试结果

然后发现效果惊人啊!不亏是百度大厂!好羡慕,好想要~

获取access_token

果断申请了个key,创建了个应用,然后发现还不够啊思考,还得鉴权认证,真是麻烦。
不过鉴权认证也好弄,按照api给的授权服务地址https://aip.baidubce.com/oauth/2.0/token发一个post请求,url带上参数:

  • grant_type: 必须参数,固定为“client_credentials”;
  • client_id: 必须参数,应用的API Key;
  • client_secret: 必须参数,应用的Secret Key;

那俩key就是你创建应用的key,示例:

1
2
3
4
https://aip.baidubce.com/oauth/2.0/token?
grant_type=client_credentials&
client_id=Va5yQRHlA4Fq4eR3LT0vuXV4&
client_secret= 0rDSjzQ20XUj5itV7WRtznPQSzr5pVw2&

调用成功后会返回你个access_token //调用接口时要用相当于你的唯一idexpires_in //Access Token有效期,其他的爱啥啥。

我当时是在本地试的遇到了跨域问题,如果你也是控制台报XMLHttpRequest cannot load http://google.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://run.jsbin.io' is therefore not allowed access.

那就是人家百度那边不允许你跨域请求了,解决办法很简单,再你的ajax里加一个dataType : jsonp就可以用jsonp跨域啦~不过!

由于jsonp的机制它会把参数放到url里传递

后面我就被这给坑了妈蛋躺血

调用识别接口

得到access_token就可以去调用接口啦
接口描述
按照文档中的接口描述,我们可以像刚才一样吧access_token放url中,请求类型也可以直接设置。body中参数有两个,一个是image,表示图像base64编码;另一个是id_card_side,代表身份证的正反面。那么问题来了,如何获取一张图片的base64编码?

这时候就要用到canvas了,canvas有一个toDataURL方法可以输出图像的信息,其中就有图像的base64编码。那么现在我们只要将获取到图片画在canvas中就可以了。借助fileReader可以简便的得到图片的src。代码如下:
bardu_ocr_test.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>文字识别测试</title>
<style>
body {
font-family: 'sourcesanspro', 'Helvetica Neue', Arial, sans-serif;
padding: 15px;
}

input {
display: block;
margin-bottom: 15px;
}

.image {
border: 1px solid #000;
}
</style>
</head>
<body>
<input type="file" name="file" id="file">
<canvas class="image" id="image"></canvas>

<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script src="baidu_ocr_test.js"></script>
</body>
</html>

baidu_ocr_test.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$(function(){
var file = $('#file');

file.on('change', function(){
var img_file_data = $(this)[0].files[0],
fileReader = new FileReader();

fileReader.readAsDataURL(img_file_data);
fileReader.onload = function(event) {
var image_src = event.target.result,
image_obj = new Image(),
image_el = document.getElementById('image'),
context = image_el.getContext('2d'),
dataURL, image_base64;

//上传图片预览
image_obj.src = image_src;
image_el.width = image_obj.width;
image_el.height = image_obj.height;
context.drawImage(image_obj, 0, 0);

//获取图片base64格式
dataURL = image_el.toDataURL('image/png');
image_base64 = dataURL.substring(dataURL.indexOf('4') + 2);
console.log(image_base64, dataURL);
};
});
});

字符串截取的有些粗糙,直接取的base64的4,呵呵~
image_base64就是我们想要的数据了,发送的时候仍然遇到了跨域问题,想还用jsonp解决的我发现不行了。。。请求都发不出去,显示414Request-URI Too Long笑哭,前面说了jsonp会把参数放到url里,一个图片的base64编码辣么长,request url的长度也是有上限的。。。

使用nodejs解决跨域问题

辣怎么办,据说放到后端处理可以避免跨域的问题,恩。。。后端,前端的后端,非nodejs莫属了!

大体思路就是前端把数据发到node,node处理下发到baidu,拿到返回值再返回给前端,感觉绕了个大圈。。。没办法,确实安全,url都不会暴露。

和js不一样,node要中规中矩的把json解析然后处理成string然后发出。server.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var http = require('http');
var querystring = require('querystring'); //用来处理json数据 类似js里的JSON
var request = require('request');

http.createServer(function(req, res) {
var request_body = '';

// 允许跨域
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);

//获取数据
req.on('data', function(chunk) {
request_body += chunk;
});

//发送请求并获取返回值
req.on('end', function() {
//设置返回到前端的数据类型为json
res.writeHead(200, {'Content-Type': 'application/json; charset=utf8'});

request_body = querystring.parse(request_body);
console.log(request_body);

request({
url: 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token=24.*************************************************9226013',
method: "POST",
json: true,
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: querystring.stringify(request_body)
}, function (error, response, body) {
if (!error && response.statusCode === 200) {

//返回识别结果
console.log(body);
res.end(JSON.stringify(body));
}
else {
console.log("error: " + error);
}
});
});
}).listen(8888); //我使用的8888吉利端口~

console.log('running');

baidu_ocr_test.js(只是ajax部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
type:'POST',
url: 'http://127.0.0.1:8888/',
data: {
image: image_base64,
id_card_side: 'front'
},
success: function(data) {
console.log(data);
},
error: function(data) {
console.log('fail');
},
});

在命令行$ node server.js把node服务跑起来再去浏览器打开html就可以调试了。成功的话能返回类似这样的json:

1
2
3
4
5
6
7
8
9
{ log_id: 2986164963,
words_result:
{ '住址': { location: [Object], words: '北京市海淀区西北旺东路100号中关村科技园' },
'公民身份号码': { location: [Object], words: '370782198709170246' },
'出生': { location: [Object], words: '19870917' },
'姓名': { location: [Object], words: '小静' },
'性别': { location: [Object], words: '女' },
'民族': { location: [Object], words: '汉' } },
words_result_num: 6 }

基本上这个功能就差不多啦,百度大厂的技术文档非常详细,错误信息的格式和解释也很准确,还能能够甄别是否有这个人,企业和个人都可以用用!

展示识别结果

前端肯定不能让数据躺在控制台,所以就写了点dom操作让数据显示在照片下面了,效果如图:
最终效果
完整代码(代码都不多都写一起了没有封装啥的随便看看):
bardu_ocr_test.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>文字识别测试</title>
<style>
body {
font-family: 'sourcesanspro', 'Helvetica Neue', Arial, sans-serif;
padding: 15px;
}

input {
display: block;
margin-bottom: 15px;
}

.image {
border: 1px solid #000;
}

ul li {
list-style: none;
}
</style>
</head>
<body>
<input type="file" name="file" id="file">
<canvas class="image" id="image"></canvas>

<div class="result" id="result">
<h4>识别结果:</h4>
</div>

<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script src="baidu_ocr_test.js"></script>
</body>
</html>

baidu_ocr_test.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$(function(){
var file = $('#file'),
result = $('#result');

file.on('change', function(){
var img_file_data = $(this)[0].files[0],
fileReader = new FileReader();

fileReader.readAsDataURL(img_file_data);
fileReader.onload = function(event) {
var image_src = event.target.result,
image_obj = new Image(),
image_el = document.getElementById('image'),
context = image_el.getContext('2d'),
dataURL, image_base64;

//上传图片预览
image_obj.src = image_src;
image_el.width = image_obj.width;
image_el.height = image_obj.height;
context.drawImage(image_obj, 0, 0);

//图片转为base64格式
dataURL = image_el.toDataURL('image/png');
image_base64 = dataURL.substring(dataURL.indexOf('4') + 2);
// console.log(image_base64, dataURL);

//发送至后端 获取识别结果
$.ajax({
type:'POST',
url: 'http://127.0.0.1:8888/',
data: {
image: image_base64,
id_card_side: 'front'
},
success: function(data) {
// console.log(data);
//显示识别结果
var el = '<ul>';

$.each(data.words_result, function(index) {
el += '<li>'+ index + ' : ' + data.words_result[index].words + '</li>';
});

el += '</ul>';
result.append($(el));
},
error: function(data) {
console.log('fail');
},
});
};
});
});

server.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var http = require('http');
var querystring = require('querystring');
var request = require('request');

http.createServer(function(req, res) {
var request_body = '';

// 允许跨域
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);

//获取数据
req.on('data', function(chunk) {
request_body += chunk;
});

//发送请求并获取返回值
req.on('end', function() {
res.writeHead(200, {'Content-Type': 'application/json; charset=utf8'});

request_body = querystring.parse(request_body);
console.log(querystring.stringify(request_body));

request({
url: 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token=24.7067***************************************9226013',
method: "POST",
json: true,
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: querystring.stringify(request_body)
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body);
res.end(JSON.stringify(body));
}
else {
console.log("error: " + error);
}
});
});
}).listen(8888);

console.log('running');