avatar

目录
旧版正方教务处验证码识别

本文以安徽工业大学的正方教务系统验证码为实例

请提前将验证码下载为gif格式图片到本地

附件1:model.ini (字模文件,已经提取好,不需修改)

原理即将图像进行二值化处理,逐一对每一个像素点进行处理,处理结果非黑即白

【业务逻辑】

1、首先从网络上下载验证码,存储到本地

2、校验图片信息,确保是gif(因为算法中采用的是gif引擎对图片处理,可根据自身需求自己修改代码中的引擎)

通过打印图片基本信息,得到以下数组
Code
1
Array ( [0] => 60 [1] => 22 [2] => 1 [3] => width=”60 ” height=”22 ” [bits] => 8 [channels] => 3 [mime] => image/gif )

3、将图像进行灰度化处理,这样可以使图像二值化难度降低,示例见下图

4、二值化处理图像

php
1
$rgbarray['red '] <= 84  && $rgbarray['green '] <= 80  && $rgbarray['blue '] <= 240

通过观察发现,将图像的RGB控制在(84, 80, 240)以内,打印输出■,否则输出□(存储在数组内),此时的识别效果最精准

5、对二值化的图像进行分割处理,该验证码排列规律性极强,故分割可以做到精确,将5个数字分割开来,将5个数字的数组分别与model.ini中的字模加以对比

6、对于经常识别出错的数字,我们对其提取特征,二次验证

7、处理结果展示

Code
1
Array ( [0] => 60 [1] => 22 [2] => 1 [3] => width="60 " height="22 " [bits] => 8 [channels] => 3 [mime] => image/gif )


上述过程都放在了DealCode.class.php

【爬取验证码】

php
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
<?php
// 爬取图片
include("DealCode.class.php");
$img = CURL("http://211.70.149.135:88/CheckCode.aspx",array(
"Cache-Control: max-age=0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language: zh-CN,zh;q=0.9",
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36",
"Upgrade-Insecure-Requests: 1",
"Host: 211.70.149.135:88",
"Proxy-Connection: keep-alive"
),
"GET",
"",
false
);
// 用了一个订单号的算法生成唯一文件名
@date_default_timezone_set("PRC");
//订购日期
$a=array();
$order_date = date('Y-m-d');
//订单号码主体(YYYYMMDDHHIISSNNNNNNNN)
$order_id_main = date('YmdHis') . rand(10000000,99999999);
//订单号码主体长度
$order_id_len = strlen($order_id_main);
$order_id_sum = 0;
for ($i=0; $i<$order_id_len; $i++) {
$order_id_sum += (int)(substr($order_id_main,$i,1));
}
//唯一订单号码(YYYYMMDDHHIISSNNNNNNNNCC)
$order_id = $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,'0',STR_PAD_LEFT);
file_put_contents('zf/'.$order_id.'.jpg',$img);
//sleep(1);
$p=new DealCode();
$t1 = microtime(true); // 识别开始时间
$p->loadVerify('zf/'.$order_id.'.jpg',0);
$codeRes=$p->OCR_CODE();
$show=$p->OCR_SHOW(); // 显示图片
$t2 = microtime(true); // 识别结束时间
?>

【DealCode.class.php源文件】

php
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
<?php
/*
*文件名:DealCode.class.php
*版本:
0.0.1
0.1.0
*时间:
2018年2月15日 14点23分
2018年3月21日 21点09分
*作者:苏喵喵
*功能:识别传来处的理后的*数字*图片,仅适用于安徽工业大学教务处系统的验证码
*/
header('content-type:text/html;charset=utf-8');
class DealCode {
//图片地址(本地)
private $imagePath;
private $origin_code;
private $code;
private $model;
//RGB阈值
private $rmax;
private $gmax;
private $bmax;
//验证码的类型
private $type;
public function __construct() {
self::classify();
}
/*
* 验证码类别分流
* @param $type
*
*/
private function classify() {
//初始化字模
$config_file_path = 'model.ini';
$config_info = parse_ini_file($config_file_path,true);
//筛选验证码类型
switch($this->type) {
//安徽工业大学验证码
case 0:
$this->model=array(
0 => $config_info['ahut']['0'],
1 => $config_info['ahut']['1'],
2 => $config_info['ahut']['2'],
3 => $config_info['ahut']['3'],
4 => $config_info['ahut']['4'],
5 => $config_info['ahut']['5'],
6 => $config_info['ahut']['6'],
7 => $config_info['ahut']['7'],
8 => $config_info['ahut']['8'],
9 => $config_info['ahut']['9'],
'v5' => $config_info['ahut']['v5'], //数字5特征提取
'v6' => $config_info['ahut']['v6'], //数字6特征提取
'v7' => $config_info['ahut']['v7'], //数字7特征提取
'v8' => $config_info['ahut']['v8'], //数字8特征提取
);
//print_r($this->model);
$this->code=array(
0 => null,
1 => null,
2 => null,
3 => null,
4 => null,
);
$this->rmax=125;
$this->gmax=94;
$this->bmax=222;
break;
default:
echo '没有该学校的识别方法!';
}
}
/*
* 载入验证码,必要步骤
* @param $imagePath
* @return $number
*/
function loadVerify($imagePath,$type) {
$im = imagecreatefromgif($imagePath);
unlink($imagePath);
if ($im && imagefilter($im, IMG_FILTER_GRAYSCALE)) {
// echo 'Image converted to grayscale.';
imagegif($im, $imagePath);
} else {
echo 'Conversion to grayscale failed.';
}
$this->imagePath=$imagePath;
$this->type=$type;
}
/*
* 【安工大教务处】验证码识别
* @param $imagePath
* @return $number
*/
public function OCR_CODE() {
$codeRes=self::getHec();
return $codeRes;
}
/*
* 显示二值化的图片
* @param $imagePath
* @return 二值化的图片
*/
public function OCR_SHOW() {
$array = getimagesize($this->imagePath);
print_r($array);
self::visualHec();
}
private function visualHec() {
$res = imagecreatefromgif($this->imagePath);
//$res = imagecreatefrompng($this->imagePath);
$size = getimagesize($this->imagePath);
echo "<pre>";
for ($i = 0; $i < $size[1]; ++$i) {
for ($j = 0; $j < $size[0]; ++$j) {
$rgb = imagecolorat($res, $j, $i);
$rgbarray = imagecolorsforindex($res, $rgb);
if ($rgbarray['red'] < ($this->rmax) && $rgbarray['green'] < ($this->gmax) && $rgbarray['blue'] < ($this->bmax)) {
echo "■";
} else {
echo "□";
}
}
echo "<br/>";
}
echo "<br/>";
echo "[完毕]";
echo "</pre>";
}
private function getHec() {
$m='';
$codeRes='';
$res = imagecreatefromgif($this->imagePath);
//$res = imagecreatefrompng($this->imagePath);
$size = getimagesize($this->imagePath);
//坐标:(j宽度,i高度)
//第一个字模的矩阵为:宽度0-13,高度所有
for ($i = 0; $i < $size[1]; ++$i) {
for ($j = 0; $j < $size[0]; ++$j) {
$rgb = imagecolorat($res, $j, $i);
$rgbarray = imagecolorsforindex($res, $rgb);
if ($rgbarray['red'] < ($this->rmax) && $rgbarray['green'] < ($this->gmax) && $rgbarray['blue'] < ($this->bmax)) {
$m = "0";
} else {
$m = "1";
}

$this->origin_code[$i][$j]=$m;
}
}
switch($this->type) {
case 0:
$codeRes=self::ahut_DealCode();
return $codeRes;
break;
case 1:
break;
default:
}
}
public function ahut_DealCode() {
for ($p=5;$p<17;$p++) {
for ($q=5;$q<14;$q++) {
$this->code[0]=$this->code[0].$this->origin_code[$p][$q];
}
}
for ($p=5;$p<17;$p++) {
for ($q=14;$q<23;$q++) {
$this->code[1]=$this->code[1].$this->origin_code[$p][$q];
}
}
for ($p=5;$p<17;$p++) {
for ($q=23;$q<32;$q++) {
$this->code[2]=$this->code[2].$this->origin_code[$p][$q];
}
}
for ($p=5;$p<17;$p++) {
for ($q=32;$q<41;$q++) {
$this->code[3]=$this->code[3].$this->origin_code[$p][$q];
}
}
for ($p=5;$p<17;$p++) {
for ($q=41;$q<50;$q++) {
$this->code[4]=$this->code[4].$this->origin_code[$p][$q];
}
}
// echo "<pre>";
// print_r($this->code);
// echo "<pre/>";
/*
* 算法优化
* 对于没有识别出来的,不到达指定匹配程度的数字进行降匹配率限制操作
*/
$codeArr=array(
0 => '-1',
1 => '-1',
2 => '-1',
3 => '-1',
4 => '-1',
);
$codeRes='';
/*校验验证码位数,是否全部识别出来,没有识别出来的,降低匹配率限制
匹配率,选出匹配率最高的*/
//初始化单字符匹配率
$percent=0;
//初始化单字符最大匹配程度存储变量
$p=0;
//初始化相似度
$sim=array(
0 => 0,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
7 => 0,
8 => 0,
9 => 0,
);
for ($i=0;$i<5;$i++) {
//++代码兼容性
if($codeArr[$i]=='-1') {
foreach($this->model as $key=>$value) {
similar_text($this->code[$i],$value,$percent);
$sim[$key]=$percent;
}
//筛选出匹配程度最大的
//筛选出来的键名$p就是验证码对应的数字;
$p=array_search(max($sim), $sim);
//$p=(string)$p;
/*
识别进化
对于上述的算法,会造成机器5、6不分,7、8不分,因此识别到5/6/7/8的数字时,要进一步对这四个数字的局部特征进行进一步比对
根据2018年3月21日实验发现,仅仅是在,
识别图片8=>数字7,暂时没有发现识别图片7=>数字8,这是单向的,8=>7
识别图片6=>数字5,暂时没有发现识别图片5=>数字6,这是单向的,6=>5
因此在识别结果为6/8的时候,再次进行校验,比对特征区域,通过则输出6/8,否则输出5/7
对于上述的算法,识别到6/8的数字时,要进一步对这2个数字的局部特征进行进一步比对
另外发现,验证码前部图片识别失败率明显高于后部,初步判定原因可能是底色分离问题导致
*/
if($p=='6') {
$vp=substr($this->code[$i],63,71);
similar_text($this->model['v5'],$vp,$vpPercentA);
similar_text($this->model['v6'],$vp,$vpPercentB);
//if($vpPercent<77.777777777778){
if($vpPercentA>$vpPercentB) {
$p='5';
}
}
if($p=='8') {
$vp=substr($this->code[$i],45,62);
similar_text($this->model['v7'],$vp,$vpPercentA);
similar_text($this->model['v8'],$vp,$vpPercentB);
//if($vpPercent<50){
if($vpPercentA>$vpPercentB) {
$p='7';
}
}
$codeArr[$i]=$p;
}
}
$codeRes=$codeArr[0].$codeArr[1].$codeArr[2].$codeArr[3].$codeArr[4];
return $codeRes;
}
}

附件:model.ini

ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ahut]
0 = 110000111100000011000110001001111001001111001001111001001111001001111001001111001000110001100000011110000111
1 = 111100111111000111110000111100100111101100111111100111111100111111100111111100111111100111111100111111100111
2 = 110000111100000011000111001001111001111111001111110011111100011111000111110001111100111111000000001000000001
3 = 110000011100000001001111001111111001111000011111000011111110001111111001001111001000110001100000011110000111
4 = 111110011111100011111100011111000011110010011110010011100110011001110011000000001000000001111110011111110011
5 = 100000011100000011100111111000111111000000111000000011001110001111111001001111001000110001100000011110000111
6 = 110000011100000001100111001001111111001000111000000011000110001001111001001111001100111001100000011110000111
7 = 000000001000000001111110011111100111111100111111001111111001111111001111110001111110011111110011111110011111
8 = 110000111100000011001111001001111001001111001100000011100000011001111001001111001001111001100000011110000111
9 = 110000111100000011001110011001111001001111001000110001100000001110001001111111001001110011000000011100000111
v5 = 111111001
v6 = 001111001
v7 = 111001111111001111
v8 = 100000011100000011

;注释
;6的特征是在初始矩阵中的第8行
;8的特征是在初始矩阵中的第6-7行
文章作者: Bill
文章链接: http://blog.webpro.ltd/2018/03/22/zf-verifyCode-ocr/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Bill's blog

评论