拆图算法/切图算法/不粘连图片切割/验证码图片切割

最近接到一个任务,需要从某网站上抓取数据,然而,打开网站时却需要输入验证码,我首先想到的是找现成的代码去使用,经过一番努力倒是找到了一些,主要都是基于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; } 
               
              
             
            
           
          
         
       
      
      
     
     
    
    
   
   

代码中注释的很清楚了,还有啥不明白的或更好想法的朋友可以提出来......

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值