Monday, June 28, 2010

HackThisSite: Programming 6 Bypass the image captcha

这是07年题目发布一来,解决耗时最长的一题,从07年年初到现在,这一晃。。。

题目地址在 http://www.hackthissite.org/missions/prog/6/

列表说明如下:
Bypass the image captcha

Bypass the image captcha This level is about OCR. Write a program which is able to read all the characters in a given image, and let it beat a captcha automatically. 

Difficulty rating: harder   Take this challenge!

题目大致是一个用 js+css 渲染的页面,上边画了一些文字,可以看这里
PHP Imagick 使用手记 这个是三年前学 php imagick 的时候做的,思路待会儿一并讲了。需要注册才能下附件,但是校外貌似没法注册 XD.
那就直接去上边给的关卡链接看吧。

画出来的页面,字符按渐近线排序。从最内顺时针方向,每旋转10度一个字符;相邻两个文字到旋转中心之间的距离是有一定比例关系的。
于是思路有两个:
1 画图 + OCR
图片绘制很简单,html代码直接可以重写成php代码,然后生成图片就是了。但是唯一的麻烦是文字的识别。
一般OCR预处理图片的时候,都会对字符进行矫正,但是毕竟斜着的字符处理起来还是很麻烦,所以基本思路是“旋转”。取的是水平和垂直的四栏数据,旋转10度,再取,再旋转。。。
由于拿到的数据本身已经做过一些round类似的处理,整数数值已经不精确,不合适地旋转会让偏差更大。所以旋转的策略应该是,每次都对*原图*旋转,第一次旋转0°,第二次10°。。。
这样总比拿上次的结果再转10°的效果好。。。

剩下就是定位,然后截框,圈字符,识别。。。。
最早按这个思路做,然后折腾了好久识别率都不高。。。
(其实是当时截框的定位计算出了错,以后有时间可以再重新实现)

2 直接字符坐标定位
每个字符都有特定的组成,例如 1 有三个横线, 2 有一个弧线和两个横线。。。
关键的一点,弧线的弧度是不受旋转影响而产生误差的。所以有弧线的字符很容易判定出来。


不管哪种思路,第一步都是差不多的:
第一步,定位所谓的旋转中心。直接字面理解即可,或者把这个发散线当一个圆,那就是圆心。
有一个重要的参数,是上边提到的相邻两个字符中间位置到圆心距离的比例,1.005,不用问为什么,反正就是这个数了。这样一来,测得最内圈是99像素左右,最外圈的是 100 * 1.005^252 ~= 351 像素。

定义的数据结构如下:
Line {
int x1,
int y1,
int x2,
int y2
}
Arc {
int x,
int y,
int radius,
int sangle,
int anglelength
}
对于Line,(x1,y1) 和 (x2,y2) 之间的位置关系是不确定的,所以预处理最好调一下顺序。
对于Arc,这里是按照逆时针方向画的弧线,所有都是用角度单位表示,x,y是圆心坐标。

预处理第一步,根据定义的数据结构,可以遍历取到x方向和y方向的最小和最大的坐标,计入 edge 中。
所以有圆心位置为:
x方向: edge[x][max] - (edge[x][max] - edge[x][min]) / (1.005^18 + 1)
y方向: edge[y][max] - (edge[y][max] - edge[y][min]) / (1.005^18 + 1)

由于题目中的坐标已经不是精确值,所以这里建议不要再作 round 处理。


*从现在开始,以上述第二种思路来分析*。
第二步,根据圆心坐标,计算每个字符的中心位置。因为旋转角度是确定的,所以这个也很容易。
第三步,遍历所有的Line 和 Arc,根据中点位置,判定基于圆心笛卡尔坐标系的角度,进而可以分辨出是在哪条射线方向上,分组整理。
第四步,对每组的Line和Arc,再判定距离圆心的位置,就可以定位大致是第几层的字符。
第五步,按组、层识别字符。

后边的思路都很简单,最恶心的就是字符的识别,特征提取。
上边说了,有弧线的地方很容易搞定。真正开发中也的确如此。但是对于没有弧线的位置,于是就得恶心了,关键还得考虑排重和缺信息的情况。

Anyway, 这个题目已经搞定。只是由于识别率的不稳定,就在那儿直接循环暴力重试。。。

不过,第一次用 php 的 goto,真方便 XD

1 comment: