本文章于2010-04-24 16:09:50发布在本人百度空间.后因某些原因临时和谐了.现在拿出来看看还是能感觉到自己一直在技术的道路上成长着.缅怀即将逝去的大学校园生活.
//////////////////////////////////////正文开始
应邀和朋友一起组队参加了学校今年的网络攻防大赛,到目前拿分不算很多,位列第五,题目类型大部分是注入和破解,难度不算太大,不过范围很广,感觉能把所有的题都靠自己的能力解出来也算是小了不起了。想把做过的几道题拿出来思考一下,总结一下对付这种比赛的少许经验。
=================================================================
=================================================================================
其中给定的图片文件是9999张(对,就是这么多:XD)BMP格式的识别码文件。解决思路:对于图像识别经常用的思路是分割,转换,采样,然后和模板数据对比,智能点的都是模糊匹配然后在匹配度的某个范围内确定图像内容。当然针对不同的目的,可以选取某些步骤,省略一些不必要且消耗时间的步骤。所以在开始实现程序之前还是先分析一下图片吧。用Photoshop来分析这些BMP位图文件,发现这些识别码比较简单都是非常格式化的数字,并且无噪声像素点背景是白色,每张图片上显示4位数字。文件格式是40*10*24bi。并且仔细计算一下后,得到每个数字只占用了10*6的像素矩阵,当然这个可以不需要顾及,按照10*10来做也是没有任何问题的,只是程序运行效率的问题。而且图像无任何噪声像素,统一的白色填充背景。知道了这些我们就可以决定如何对图像进行采样了。可以先构造一个6*10的二位数组dataTemp[][]所以如果我们直接得到位图数据的数组后,对位图的每个像素点求RGB值,如果等于背景色的RGB值我们就就让dataTemp的对应位置的值取0,否则取1.,这样做就可以不用考虑其他色彩的问题了。采样完成后我们就得到了一个从源图像映射过来的数组,把这个数组和模板数据匹配就可以得到这个图像显示的数字了。这样做我们就可以省略了讲图像转换为单色图像的步骤,也正因为如此本文中的实现方法不具有通用性。
这只是说明了处理的大致思路,至于实现方法有很多,纯C预言文件操作,C+windowsAPI,C#,PHP脚本,当然还有JAVA。不过是工作量的问题,因为纯C和windowsAPI需要知道BMP文件格式的相关知识,比如BMP文件中位图数据起始偏移量等,而且需要用数学控制采样时的扫描次序,所以比较繁杂。这样一来,脚本或者JAVA或者C#就成了高效的实现方法。这里我采用JAVA来实现。
实现先导步骤:
第一步,分割图片,因为每张图片四位数,所以分割成四个子图。
第二步,对一个子图进行像素点采样,然后生成映射的数组。
第三步,将得到的数组与模板数组匹配,得到图片显示内容。
注:模板数组的生成方法需要对单张图片进行采样然后对照图片内容添加到程序中。
代码示例:
/*********************************************************** * * BMP image process routine * 2011/04/20 tishion @ CUIT * [email protected] * ************************************************************/ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import javax.imageio.ImageIO; public class RecgBmp { public static void main(String args[]) throws Exception { //对9999张图片识别,求乘积,求和 long SUM = 0; for(int i=1;i<=9999;i++){ String filename = "c:\\bmp\\".concat(String.valueOf(i)).concat(".bmp"); String s = Bmp2Num.recognize(ImageIO.read(new File(filename))); System.out.println(s); SUM = SUM + i*Long.parseLong(s); } System.out.println("SUM = " + SUM); } } //验证码识别类 class Bmp2Num{ // 数字模板 0-9 static int[][] value = { // num 0; { 0,1,1,1,1,0,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0 }, // num 1 { 0,0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0 }, // num2 { 0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1 }, // num3 { 0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0 }, // num4 { 0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,1,0,1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,1,1 }, // num5 { 1,1,1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0 }, // num6 { 0,0,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0 }, // num7 { 1,1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0 }, // num8 { 0,1,1,1,1,0,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0 }, // num9 { 0,1,1,1,1,0,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,1,1,0,1,1,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,1,1,1,0,0 } }; public static String recognize(byte[] byteArray) throws Exception { InputStream is = new ByteArrayInputStream(byteArray); BufferedImage image = ImageIO.read(is); return recognize(image); } /*识别图像*/ public static String recognize(BufferedImage image) throws Exception { StringBuffer sb = new StringBuffer(""); BufferedImage newim[] = new BufferedImage[4]; if(null == image){ throw new RuntimeException("iamage is null"); } // 图像分成四块 newim[0] = image.getSubimage(0, 0, 6, 10); newim[1] = image.getSubimage(10, 0, 6, 10); newim[2] = image.getSubimage(20, 0, 6, 10); newim[3] = image.getSubimage(30, 0, 6, 10); for (int k = 0; k < 4; k++) { int iw = newim[k].getWidth(null); int ih = newim[k].getHeight(null); int[] pix = new int[iw * ih]; // 转换为0,1的图像数组。扫描图像数据,像素为白色的取值为0,否则取值1; for (int i = 0; i < ih; i++) { for (int j = 0; j < iw; j++) { pix[i * (iw) + j] = newim[k].getRGB(j, i); //System.out.print(pix[i * (iw) + j]); if (pix[i * (iw) + j] == -1118482)//-1118482是空白像素的值 pix[i * (iw) + j] = 0; else pix[i * (iw) + j] = 1; //System.out.print(pix[i * (iw) + j]); } //System.out.println(); } //System.out.println();System.out.println(); // 得到像匹配的数字。 int r = getMatchNum(pix); sb.append(r); } return sb.toString(); } /*位图转换成的0、1数组和模板数组进行比较,返回匹配的数字*/ private static int getMatchNum(int[] pix) { int result = -1; int temp = 100; int x; for (int k = 0; k <= 9; k++) { x = 0; for (int i = 0; i < pix.length; i++) { x = x + Math.abs(pix[i] - value[k][i]); } if(x == 0){ result = k; break; }else if (x < temp){ temp = x; result = k; } } return result; } }
网上有很多关于验证码识别的解决方法,其中大多数都是比较智能化的,因为都采用了图像转换的步骤,所以与通用性很强。这里只是为了解决临时问题而实现的一种比较偷懒的解决方法。毕竟重在结果,能节省时间也不失为一种明智的选择。