在面对那种有许多干扰线或者干扰点的验证码,或者各种各样的验证码的时候,往往一个阀值是无法精确的处理图形验证码的,这里,我们主要使用一个范围缩圈进行像素点的比对,因为之前有使用计算像素的平均值K作为阈值,但是会导致部分对象像素或者背景像素丢失,故这个方案暂且搁置。
我们知道每一个像素点都有自己的像素值,但是对于一张验证码来说,同一个字母颜色或深或浅,其像素点的值都会有差异,所以我们需要一个容错范围,我们可以指定,在某个范围差内的值,都是这个图片字母的值。然后我们让这个阀值在一个范围内不断递增,从而得到不同阀值下的二值化图片。
这里有一个处理前和处理后图片的对比:
处理前的图片
处理后我们得到很多张图片,从中选出最完整的一张图片:
因为机器学习的前期图片还是需要人工筛选一下的。
以下是我们处理图片的代码:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class PicDeal {
//todo splitNums可以根据你给到的图片色差进行调整,在你自己使用时,可以针对splitNums做一个循环,每次加多少,得到不同的色差比的二值化后的图片,因为不同的图片可能干扰线、干扰点颜色原因,二值化后会有差异
//todo splitWidthNum:把图片根据长度切分的分数,这个可以根据你图片中的数字个数进行切分
public static int splitNums=2100000;
public static int beginNum=100000;
public static int endNum=4000000;
public static final int splitWidthNum=1;
public static void main(String[] args) {
//todo 需要处理图片的路径
String path="C://Users/admin/Desktop/微信搜狗验证码/yzmsg/3.jpg";
try{
splitPic(path);
}catch (Exception e){
e.printStackTrace();
}
}
//todo 分割图片
public static void splitPic(String picFile) throws Exception{
//todo 分割图片
// BufferedImage img = ImageIO.read(new File(picFile));
Image image = ImageIO.read(new File(picFile));
for (int i = 0; i < splitWidthNum; i++) {
String filename="F://test/test3_"+i+".jpg";
ImageIO.write(toBufferedImage(image), "JPG", new File(filename));
//todo 分割后的图片处理
for (int j=beginNum;j<=endNum;j+=25000){
Image thisimg = ImageIO.read(new File(filename));
BufferedImage imgDeal=removeBackgroud(thisimg,j,i);
imgDeal=DeletePassPoint.deletepoints(imgDeal);
//todo 分割后图片写出去
String filename2="F://test/test3_"+i+"imgDeal_像素分割_"+j+".jpg";
ImageIO.write(imgDeal, "JPG", new File(filename2));
}
}
}
//todo 图片处理算法
public static BufferedImage removeBackgroud(Image img2,int j,int i)throws Exception {
BufferedImage img=toBufferedImage(img2);
String filename="F://test/test3_"+i+"_像素分割_"+j+".jpg";
ImageIO.write(img, "JPG", new File(filename));
splitNums=j;
System.out.println("splitNums==========="+splitNums);
img = img.getSubimage(1, 1, img.getWidth()-2, img.getHeight()-2);
int width = img.getWidth();
int height = img.getHeight();
double subWidth = (double) width;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//todo 以下是对图片进行二值化处理,在这里我的思路是规定,色差范围在splitNums到负splitNums之间的,算是同色,放入同一个色值,放入一个map中,
//todo map中的Key放色值,value放这个色值得个数,后期就根据这个色值来对验证码进行二值化
for (int x = (int) (1 + 0 * subWidth); x < (0 + 1) * subWidth && x < width - 1; ++x) {
for (int y = 0; y < height; ++y) {
if (isWhite(img.getRGB(x, y)) == 1){
continue;
}
Map<Integer, Integer> map2 = new HashMap<Integer, Integer>();
for (Integer color : map.keySet()) {
map2.put(color,map.get(color));
}
boolean hasnewColor=false;
for (Integer color : map2.keySet()) {
// System.out.println(Math.abs(color)-Math.abs(img.getRGB(x, y)));
// Math.abs(color)-Math.abs(img.getRGB(x, y)))<splitNums&&Math.abs(color)-Math.abs(img.getRGB(x, y))>-splitNums
if (Math.abs(color)-Math.abs(img.getRGB(x, y))<splitNums&&Math.abs(color)-Math.abs(img.getRGB(x, y))>-splitNums){
map.put(color, map.get(color) + 1);
hasnewColor=true;
}
}
if (!hasnewColor){
map.put(img.getRGB(x, y), 1);
}
if (map.isEmpty()){
map.put(img.getRGB(x, y), 1);
}
}
}
System.out.println("==============================");
int max = 0;
int colorMax = 0;
for (Integer color : map.keySet()) {
if (max < map.get(color)) {
max = map.get(color);
colorMax = color;
}
}
map.remove(colorMax);
max = 0;
int colorMax2 = 0;
for (Integer color : map.keySet()) {
if (max < map.get(color)) {
max = map.get(color);
colorMax2 = color;
}
}
map.remove(colorMax2);
// int colorMax3 = 0;
// for (Integer color : map.keySet()) {
// if (max < map.get(color)) {
// max = map.get(color);
// colorMax3 = color;
// }
// }
// colorMax=colorMax2;
// colorMax2=colorMax3;
//todo 核心算法
for (int x = (int) (1 + 0 * subWidth); x < (0 + 1) * subWidth&& x < width - 1; ++x) {
for (int y = 0; y < height; ++y) {
int ress=Math.abs(img.getRGB(x, y))-Math.abs(colorMax);
int ress_2=Math.abs(Math.abs(colorMax)-Math.abs(img.getRGB(x, y)));
int ress2=Math.abs(img.getRGB(x, y))-Math.abs(colorMax2);
if (ress>0&&ress2>0){
if (ress>ress2){
if ((ress2<splitNums&&ress2>-splitNums)) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}else{
// img.setRGB(x, y, Color.WHITE.getRGB());
if (ress2<splitNums&&ress2>ress) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}
}else if (ress<0&&ress2<0){
ress=Math.abs(ress);
ress2=Math.abs(ress2);
if (ress>ress2){
if ((ress2<splitNums&&ress2>-splitNums)) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}else{
if (ress2<splitNums&&ress2>ress) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}
}else if (ress>0&&ress2<0){
if (Math.abs(ress)>Math.abs(ress2)){
if ((Math.abs(ress2)>splitNums&&ress2<-splitNums)) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}else{
img.setRGB(x, y, Color.WHITE.getRGB());
// if (splitNums<)
}
}else{
if (Math.abs(ress)>Math.abs(ress2)){
if ((Math.abs(ress2)>splitNums&&ress2<-splitNums)) {
img.setRGB(x, y, Color.BLACK.getRGB());
} else {
img.setRGB(x, y, Color.WHITE.getRGB());
}
}else{
img.setRGB(x, y, Color.WHITE.getRGB());
// if (splitNums<)
}
// img.setRGB(x, y, Color.BLACK.getRGB());
}
}
}
return img;
}
//todo 判断是否为白色的方法 爬虫 验证码处理
public static int isWhite(int colorInt) {
Color color = new Color(colorInt);
if (color.getRed() + color.getGreen() + color.getBlue()>600) {
return 1;
}
return 0;
}
//todo image转bufferImage
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage)image;
}
// 加载所有像素
image = new ImageIcon(image).getImage();
BufferedImage bimage = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
int transparency = Transparency.OPAQUE;
// 创建buffer图像
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(
image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
e.printStackTrace();
}
if (bimage == null) {
int type = BufferedImage.TYPE_INT_RGB;
bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}
// 复制
Graphics g = bimage.createGraphics();
// 赋值
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
}
在这里我们默认RGB最多的为背景色,所以依照RGB颜色第二多的为准进行归类划值。
在这里面我们也进行了一波简单的处理,就是对得到的图片进行单独点的删除,因为处理后的图片会有类似这张图一样的小点:
,针对这类的小点,我们默认去掉。
算法其实很简单,就是当判定上下左右其中3个方位为空时,此点算离散点。
代码如下:
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
public class DeletePassPoint {
public static BufferedImage deletepoints(BufferedImage img){
//todo 为白色的像素点rgb值
// int[] whitePointNum={-257,-1,-770,-1027,-514};
int width = img.getWidth();
int height = img.getHeight();
double subWidth = (double) width;
ColorModel cm = img.getColorModel();
BufferedImage imgNew=new BufferedImage(cm,cm.createCompatibleWritableRaster(img.getWidth(),img.getHeight()),cm.isAlphaPremultiplied(), null);
for (int x = (int) (1 + 0 * subWidth); x < (0 + 1) * subWidth && x < width - 1; ++x) {
for (int y = 1; y < height-1; ++y) {
int leftRgb=img.getRGB(x-1,y);
int rightRgb=img.getRGB(x+1,y);
int buttonRgb=img.getRGB(x,y-1);
int headRgb=img.getRGB(x,y+1);
int judgy=0;
//todo 对自己本身像素点周边进行检测
if (isWhite(leftRgb)==0){
judgy++;
}
if (isWhite(rightRgb)==0){
judgy++;
}
if (isWhite(buttonRgb)==0){
judgy++;
}
if (isWhite(headRgb)==0){
judgy++;
}
//todo 当判定上下左右其中3个方位为空时,此点算离散点
if (judgy>=3){
// imgNew.setRGB(x, y, Color.BLACK.getRGB());
imgNew.setRGB(x, y, Color.WHITE.getRGB());
}else{
// imgNew.setRGB(x, y, Color.WHITE.getRGB());
imgNew.setRGB(x, y, Color.BLACK.getRGB());
}
}
}
return imgNew;
}
//todo 判断是否为白色的方法 爬虫 验证码处理
public static int isWhite(int colorInt) {
Color color = new Color(colorInt);
if (color.getRed() + color.getGreen() + color.getBlue()>600) {
return 1;
}
return 0;
}
}
这样下来,处理出来的图片就是我们刚才贴出来的样子了。当然,这个方案还有很多需要改进的地方。
比如:当背景颜色RGB值并不是最多的时候,这样就会出现误删除。还有就是验证码RGB色和背景颜色相差不大时,得到的也是失真的图片。
当然,后期如果有什么更好的方案处理图片,会不断更新,也欢迎各种观众大佬爷提出建议,不断精进!