Captchacker.java
package com.hjr.apollo.image; import com.hjr.apollo.image.ImageUtils.RGBVector; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * 圖片驗證碼破解 */ public class Captchacker { public static void main(String[] args) throws IOException { String capthcaDir = "d:\\captcha\\"; String captchaUrl = "http://118.122.127.40:8989/servlet/ValidateCodeServlet?d=" + System.currentTimeMillis(); BufferedImage captcha = ImageUtils.readImageFromUrl(captchaUrl); ImageUtils.saveImage(captcha, capthcaDir + System.currentTimeMillis() + ".png"); // 圖片二值化 BufferedImage binaryImage = toBinary(captcha); ImageUtils.saveImage(binaryImage, capthcaDir + System.currentTimeMillis() + "-b.png"); // 刪除邊線 BufferedImage noBorderImage = clearBorder(binaryImage); ImageUtils.saveImage(noBorderImage, capthcaDir + System.currentTimeMillis() + "-nb.png"); // 去噪點 BufferedImage noNoicePointImage = clearNoicePoint(noBorderImage); ImageUtils.saveImage(noNoicePointImage, capthcaDir + System.currentTimeMillis() + "-cp.png"); // 去幹擾線 BufferedImage noNoiceLingImage = clearNoiceLine(noNoicePointImage); ImageUtils.saveImage(noNoiceLingImage, capthcaDir + System.currentTimeMillis() + "-cl.png"); // 驗證碼字符切割 List<BufferedImage> subImgs = splitByChar(noNoiceLingImage);//流水算法 for (int i = 0; i < subImgs.size(); i++) { BufferedImage bi = subImgs.get(i); ImageUtils.saveImage(bi, capthcaDir + System.currentTimeMillis() + "-sub" + i + ".png"); } // 大小歸一化 } /** * 把圖片進行二值化處理 * @param image 待處理的圖片 * @return */ public static BufferedImage toBinary(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); int whiteRGB = Color.WHITE.getRGB(); int blackRGB = Color.BLACK.getRGB(); for (int i = 0 ; i < width ; i++) { for (int j = 0 ; j < height; j++) { RGBVector rgb = ImageUtils.parseRGBVector(image.getRGB(i, j)); int min = Math.min(rgb.getRed(), rgb.getGreen()); min = Math.min(min, rgb.getBlue()); if (min > 200) { binaryImage.setRGB(i, j, whiteRGB); } else { binaryImage.setRGB(i, j, blackRGB); } } } return binaryImage; } /** * 刪除圖片邊線 * @param image 待處理圖片 * @return */ public static BufferedImage clearBorder(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); int whiteRGB = Color.WHITE.getRGB(); for (int i = 0 ; i < width ; i++) { for (int j = 0 ; j < height; j++) { // 邊線點設爲白色點 if (i == 0 || j == 0 || i == width - 1 || j == height - 1) { binaryImage.setRGB(i, j, whiteRGB); continue; } binaryImage.setRGB(i, j, image.getRGB(i, j)); } } return binaryImage; } /** * 刪除噪點:八鄰域 * @param image 待處理圖片 * @return */ public static BufferedImage clearNoicePoint(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); int whiteRGB = Color.WHITE.getRGB(); int blackRGB = Color.BLACK.getRGB(); for (int i = 0 ; i < width; i++) { for (int j = 0 ; j < height; j++) { int rgb = image.getRGB(i, j); if (i == 0 || j == 0 || i == width - 1 || j == height - 1) { binaryImage.setRGB(i, j, rgb); continue; } if (rgb == whiteRGB) { binaryImage.setRGB(i, j, whiteRGB); continue; } // 判斷八鄰域是否有黑點,有則保存 if (blackRGB == image.getRGB(i-1, j) || blackRGB == image.getRGB(i-1, j-1) || blackRGB == image.getRGB(i-1, j+1) || blackRGB == image.getRGB(i, j-1) || blackRGB == image.getRGB(i, j+1) || blackRGB == image.getRGB(i+1, j) || blackRGB == image.getRGB(i+1, j-1) || blackRGB == image.getRGB(i+1, j+1)) { binaryImage.setRGB(i, j, blackRGB); continue; } binaryImage.setRGB(i, j, whiteRGB); } } return binaryImage; } /** * 刪除干擾線:八鄰域 * @param image 待處理圖片 * @return */ public static BufferedImage clearNoiceLine(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); int whiteRGB = Color.WHITE.getRGB(); int blackRGB = Color.BLACK.getRGB(); for (int i = 0 ; i < width; i++) { for (int j = 0 ; j < height; j++) { int rgb = image.getRGB(i, j); if (i == 0 || j == 0 || i == width - 1 || j == height - 1) { binaryImage.setRGB(i, j, rgb); continue; } if (rgb == whiteRGB) { binaryImage.setRGB(i, j, whiteRGB); continue; } int sum = blackRGB == image.getRGB(i-1, j) ? 1 : 0; sum += blackRGB == image.getRGB(i-1, j-1) ? 1 : 0; sum += blackRGB == image.getRGB(i-1, j+1) ? 1 : 0; sum += blackRGB == image.getRGB(i, j-1) ? 1 : 0; sum += blackRGB == image.getRGB(i, j+1) ? 1 : 0; sum += blackRGB == image.getRGB(i+1, j) ? 1 : 0; sum += blackRGB == image.getRGB(i+1, j-1) ? 1 : 0; sum += blackRGB == image.getRGB(i+1, j+1) ? 1 : 0; // 8鄰域黑點數小於4個斷定爲干擾線上的點 binaryImage.setRGB(i, j, sum < 4 ? whiteRGB : blackRGB); } } return binaryImage; } /** * 流水算法"精華部分.有空隙的圖完全拆開(好比"回"字會被拆成大小倆"口"字) * @param image 須要拆解的圖(java.awt.image.BufferedImage) */ private static List<BufferedImage> splitByChar(BufferedImage image) { // 用於裝填拆解後的圖片碎塊 List<BufferedImage> subImgs = new ArrayList<BufferedImage>(); int minPix = 50;// 單個圖片最小的像素數,下面會拋棄小於這個像素數的小圖塊 // 獲取圖片寬高 int width = image.getWidth(); int height = image.getHeight(); // 用於裝填每一個圖塊的點數據 List<HashMap<Point, Integer>> pointList = new ArrayList<HashMap<Point, Integer>>(); // 根據寬高輪詢圖片中的全部點進行計算 for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { //找到一個點 label: if (isBlack(image.getRGB(x, y))) { Point point = new Point(x, y);//java.awt.Point for (HashMap<Point, Integer> pointMap : pointList) { if (pointMap.get(point) != null) { break label;//跳到標籤處,此時不會再執行下面的內容. } } HashMap<Point, Integer> pointMap = new HashMap<Point, Integer>(); // 這個用法很關鍵,根據Map的KEY值不能重複的特色避免重複填充point pointMap.put(point, 1); // 這裏就是在流水啦... get4Point(x, y, image, pointMap); pointList.add(pointMap); break; } } } // 根據提取出來的point建立各個碎圖塊 for (int i = 0; i < pointList.size(); ++i) { HashMap<Point, Integer> 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 < minPix) continue; // 建立個圖片空殼子(裏面的全部點值都是0,即黑色) BufferedImage imgCell = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // 先將全部點替換成白色(反正我沒有找到new BufferedImage的時候能夠初始化像素色值的) for (int x = 0; x < w; ++x) { for (int y = 0; y < h; ++y) { imgCell.setRGB(x, y, 0xffffffff);//圖片換成白底 } } // 對應點換成黑色(若是上面換成白底的一步不作的話下面這裏能夠替換成白色,就變成了黑底白色的碎圖了) for (Point p : pointMap.keySet()) { imgCell.setRGB(p.x - l, p.y - t, 0); } // 將切好的圖放入上文傳入的容器中(不傳入容器的話這裏能夠用於返回) subImgs.add(imgCell); } // 耗時 return subImgs; } /** * 填進來上下左右中不是白色的點 * 遞歸 * @return */ private static void get4Point(int x, int y, BufferedImage img, HashMap<Point, Integer> 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; } }
ImageUtils.java
package com.hjr.apollo.image; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; /** * 圖片處理工具集 */ public class ImageUtils { /** * 從指定連接加載圖片信息 * @param url 圖片連接 * @return */ public static BufferedImage readImageFromUrl(String url) throws IOException { return ImageIO.read(new URL(url)); } /** * 把圖片保存到指定路徑 * @param im 圖片信息 * @param imagePath 圖片待保存路徑 * @throws IOException */ public static void saveImage(RenderedImage im, String imagePath) throws IOException { saveImage(im, "png", imagePath); } /** * 把圖片按指定格式,保存到指定路徑 * @param im 圖片信息 * @param formatName 圖片待保存格式 * @param imagePath 圖片待保存路徑 * @throws IOException */ public static void saveImage(RenderedImage im, String formatName, String imagePath) throws IOException { ImageIO.write(im,formatName, new File(imagePath)); } /** * 解析R、G、B三個份量的值 * @param argb * @return */ public static RGBVector parseRGBVector(int argb) { return RGBVector.parseRGBVector(argb); } public static class RGBVector { private int red; private int green; private int blue; public static RGBVector parseRGBVector(int argb) { return new RGBVector(argb); } public RGBVector() { } public RGBVector(int argb) { red = argb >> 16 & 0xFF; green = argb >> 8 & 0xFF; blue = argb & 0xFF; } public int getRed() { return red; } public void setRed(int red) { this.red = red; } public int getGreen() { return green; } public void setGreen(int green) { this.green = green; } public int getBlue() { return blue; } public void setBlue(int blue) { this.blue = blue; } } }