最近接到一个任务,需要从某网站上抓取数据,然而,打开网站时却需要输入验证码,我首先想到的是找现成的代码去使用,经过一番努力倒是找到了一些,主要都是基于OCR啥啥的库去解析验证码。然而......这并没有什么卵用!这些代码存在的统一的问题就是解析起来成功率太低,甚至可以说是乱七八糟。想来各个验证码有不同的表现形式,所谓的OCR库也是难以面面俱到吧。种种原因,不得不自己写验证码解析,无奈之下网上查找验证码的解析技术,此处是我所看的一系列验证码解析的技术博客,感谢大神给我提供是思路,也让我知道验证码解析原来是这样的!!上链接:http://blog.csdn.net/problc/article/details/5794460
看了这些文章后我大致了解了验证码解析的基本原理和步骤:降噪,切图,对比。
然而找遍了各个网站,切图的方式都是横切或竖切,如图:
然而遇到下面样式的验证码就傻眼了:
(随手画的,将就看吧)
从图片上可以看出来,文字是倾斜的,甚至有些摆放不规律的现象。这种情况横切或者竖切都无法完全将A,B,C,D分开了。网上也到处翻找没有找到能实现这种要求的切图方式。无奈,要么放弃,要么自己写个算法搞定它!各位看官你们有福了,因为我属于后者......
我的算法是从水的特点来考虑的,不妨将A,B,C,D看成四个不同形状的凹槽,当水从每个凹槽的一点倒入后,根据谁的流动性总会充满整个凹槽,而相邻的其他凹槽却不会有水。根据这个特点我灵机一动有了下面算法的实现,此处遍称之为“流水算法”吧,往往不要脸的人会在一些算法前面带上自己的名字以便扬名立万,我对这种人深深地不齿!嗯哼!好吧!就叫“张统强流水算法”吧!^_^
好吧,废话也说了不少了,扬名立万的事也做了,下面开始上代码:
/**测试(想要找个测试图片的话就自己画一个去....)
* @param args
*/
public static void main(String[] args)
{
File dirYZM = new File("E:/YZM/"); //验证码存放目录
if(!dirYZM.exists())dirYZM.mkdirs();
File[] filesYZM = dirYZM.listFiles(); //取得所有验证码图片(根据自己需要)
for (File fileYZM : filesYZM) {
try {
BufferedImage curImg = ImageIO.read(fileYZM);
List subImgs = new ArrayList();
getImgCell(curImg,subImgs);//流水算法
for(BufferedImage imgItem:subImgs)
ImageIO.write(imgItem, Common.imgType, new File("E:/YZM/"+fileYZM.getName()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**流水算法"精华部分.有空隙的图彻底拆开(比如"回"字会被拆成大小俩"口"字)
* @param img 需要拆解的图(java.awt.image.BufferedImage)
* @param subImgs 用于装填拆解后的图片碎块
*/
private static void getImgCell(BufferedImage img,List subImgs)
{
long startTime = System.currentTimeMillis();//用于查看算法耗时
int minPix = 50;//单个图片最小的像素数,下面会抛弃小于这个像素数的小图块
//获取图片宽高
int width = img.getWidth();
int height = img.getHeight();
//用于装填每个图块的点数据
List> pointList = new ArrayList>();
//根据宽高轮询图片中的所有点进行计算
for(int x=0;x pointMap:pointList){
if(pointMap.get(point)!=null){
break label;//跳到标签处,此时不会再执行下面的内容.
}
}
HashMap pointMap = new HashMap();
//这个用法很关键,根据Map的KEY值不能重复的特点避免重复填充point
pointMap.put(point,1);
//这里就是在流水啦...
get4Point(x, y, img, pointMap);
pointList.add(pointMap);
break;
}
}
}
//根据提取出来的point创建各个碎图块
for(int i=0;i pointMap = pointList.get(i);
//图片的左,上,右,下边界以及宽,高
int l=0,t=0,r=0,b=0,w=0,h=0,index=0;
for(Point p:pointMap.keySet())
{
if(index == 0){
//用第一个点来初始化碎图的四个边界
l=p.x;t=p.y;r=p.x;b=p.y;
}else{
//再根据每个点与原有的点进行比较取舍四个边界的值
l=Math.min(l, p.x);
t=Math.min(t, p.y);
r=Math.max(r, p.x);
b=Math.max(b, p.y);
}
index++;
}
w=r-l+1;h=b-t+1;
//去除杂点(小于50像素数量的点集不要)
if(w * h pointMap)
{
//左边
Point pl = new Point(x-1, y);
if(x-1>=0 && isBlack(img.getRGB(x-1, y)) && pointMap.get(pl)==null){
pointMap.put(pl,1);
get4Point(x-1, y,img,pointMap);
}
//右边
Point pr = new Point(x+1, y);
if(x+1 < img.getWidth() && isBlack(img.getRGB(x+1, y)) && pointMap.get(pr)==null){
pointMap.put(pr,1);
get4Point(x+1, y,img,pointMap);
}
//上边
Point pt = new Point(x, y-1);
if(y-1>=0 && isBlack(img.getRGB(x, y-1)) && pointMap.get(pt)==null){
pointMap.put(pt,1);
get4Point(x, y-1,img,pointMap);
}
//下边
Point pb = new Point(x, y+1);
if(y+1 < img.getHeight() && isBlack(img.getRGB(x, y+1)) && pointMap.get(pb)==null){
pointMap.put(pb,1);
get4Point(x, y+1,img,pointMap);
}
}
/**判断是不是黑色[这里的黑色指暗色],实际上本程序处理过的颜色,黑就是纯黑,值=0
* @param colorInt
* @return
*/
public static boolean isBlack(int colorInt) {
int threshold=150;//色域,用于界定多少范围的色值是噪色
Color color = new Color(colorInt);
return color.getRed() + color.getGreen() + color.getBlue() <= threshold*3;
}
代码中注释的很清楚了,还有啥不明白的或更好想法的朋友可以提出来......