java圖像處理:灰度化,二值化,降噪,切割,裁剪,識別,找相似等


前段時間做爬蟲,涉及到對圖片驗證碼的破解,這里羅列一些常用的圖像處理方法,都很簡單並沒用到什么復雜的算法,所以不涉及opencv,都是一些直接對rgb像素點的操作,很簡單也很好理解,至於識別直接用的tesseract-ocr,也可以用svm。(ps:圖片的像素值矩陣的原點在左上角,上邊是x軸,左邊是y軸)

1、灰度化和二值化,即把彩色圖片經過灰度化和二值化變成只有黑白(只有0,1的矩陣)的數據,便於后續對圖片的處理

public static BufferedImage grayImage(BufferedImage bufferedImage) throws Exception {
 
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
 
        BufferedImage grayBufferedImage = new BufferedImage(width, height, bufferedImage.getType());
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                final int color = bufferedImage.getRGB(i, j);
                final int r = (color >> 16) & 0xff;
                final int g = (color >> 8) & 0xff;
                final int b = color & 0xff;
                int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
                int newPixel = colorToRGB(255, gray, gray, gray);
                grayBufferedImage.setRGB(i, j, newPixel);
            }
        }
 
        return grayBufferedImage;
 
    }
 
    /**
     * 顏色分量轉換為RGB值
     * 
     * @param alpha
     * @param red
     * @param green
     * @param blue
     * @return
     */
    private static int colorToRGB(int alpha, int red, int green, int blue) {
 
        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red;
        newPixel = newPixel << 8;
        newPixel += green;
        newPixel = newPixel << 8;
        newPixel += blue;
 
        return newPixel;
 
    }
 
    public static BufferedImage binaryImage(BufferedImage image) throws Exception {
        int w = image.getWidth();  
        int h = image.getHeight();  
        float[] rgb = new float[3];  
        double[][] zuobiao = new double[w][h];  
        int black = new Color(0, 0, 0).getRGB();  
        int white = new Color(255, 255, 255).getRGB();  
        BufferedImage bi= new BufferedImage(w, h,  
                BufferedImage.TYPE_BYTE_BINARY);;  
        for (int x = 0; x < w; x++) {  
            for (int y = 0; y < h; y++) {  
                int pixel = image.getRGB(x, y);   
                rgb[0] = (pixel & 0xff0000) >> 16;  
                rgb[1] = (pixel & 0xff00) >> 8;  
                rgb[2] = (pixel & 0xff);  
                float avg = (rgb[0]+rgb[1]+rgb[2])/3;  
                zuobiao[x][y] = avg;      
                  
            }  
        }
            //這里是閾值,白底黑字還是黑底白字,大多數情況下建議白底黑字,后面都以白底黑字為例
        double SW = 192;  
        for (int x = 0; x < w; x++) {  
            for (int y = 0; y < h; y++) {  
                if (zuobiao[x][y] < SW) {  
                    bi.setRGB(x, y, black);  
                }else{  
                    bi.setRGB(x, y, white);  
                }  
            }             
        }  
        
        
        
        return bi;
    }
        // 自己加周圍8個灰度值再除以9,算出其相對灰度值
    public static double getGray(double[][] zuobiao, int x, int y, int w, int h) {
        double rs = zuobiao[x][y] + (x == 0 ? 255 : zuobiao[x - 1][y]) + (x == 0 || y == 0 ? 255 : zuobiao[x - 1][y - 1])
                + (x == 0 || y == h - 1 ? 255 : zuobiao[x - 1][y + 1]) + (y == 0 ? 255 : zuobiao[x][y - 1])
                + (y == h - 1 ? 255 : zuobiao[x][y + 1]) + (x == w - 1 ? 255 : zuobiao[x + 1][y])
                + (x == w - 1 || y == 0 ? 255 : zuobiao[x + 1][y - 1])
                + (x == w - 1 || y == h - 1 ? 255 : zuobiao[x + 1][y + 1]);
        return rs / 9;
    }

2、降噪(領域檢測法),針對二值化后的圖片來說(白底黑字),噪點就是圖片中一堆密集黑色像素點中少許白色像素點(字體里的白點或背景里的黑點),這時,解決方法可以遍歷像素點,一個像素點周圍有8個像素點,如果這個像素點周圍有6個以上像素點是黑色就可以把這個像素點也認為是黑色:

/**
     * 降噪,以1個像素點為單位(實際使用中可以循環降噪,或者把單位可以擴大為多個像素點)
     * @param image
     * @return
     */
    public static BufferedImage denoise(BufferedImage image){
        int w = image.getWidth();  
        int h = image.getHeight();
        int white = new Color(255, 255, 255).getRGB();  
 
        if(isWhite(image.getRGB(1, 0)) && isWhite(image.getRGB(0, 1)) && isWhite(image.getRGB(1, 1))){
            image.setRGB(0,0,white);
        }
        if(isWhite(image.getRGB(w-2, 0)) && isWhite(image.getRGB(w-1, 1)) && isWhite(image.getRGB(w-2, 1))){
            image.setRGB(w-1,0,white);
        }
        if(isWhite(image.getRGB(0, h-2)) && isWhite(image.getRGB(1, h-1)) && isWhite(image.getRGB(1, h-2))){
            image.setRGB(0,h-1,white);
        }
        if(isWhite(image.getRGB(w-2, h-1)) && isWhite(image.getRGB(w-1, h-2)) && isWhite(image.getRGB(w-2, h-2))){
            image.setRGB(w-1,h-1,white);
        }
        
        for(int x = 1; x < w-1; x++){
            int y = 0;
            if(isBlack(image.getRGB(x, y))){
                int size = 0;
                if(isWhite(image.getRGB(x-1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y+1))){
                    size++;
                }
                if(isWhite(image.getRGB(x-1, y+1))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y+1))){
                    size++;
                } 
                if(size>=5){
                    image.setRGB(x,y,white);                     
                }
            }
        }
        for(int x = 1; x < w-1; x++){
            int y = h-1;
            if(isBlack(image.getRGB(x, y))){
                int size = 0;
                if(isWhite(image.getRGB(x-1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y-1))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y-1))){
                    size++;
                }
                if(isWhite(image.getRGB(x-1, y-1))){
                    size++;
                }
                if(size>=5){
                    image.setRGB(x,y,white);                     
                }
            }
        }
        
        for(int y = 1; y < h-1; y++){
            int x = 0;
            if(isBlack(image.getRGB(x, y))){
                int size = 0;
                if(isWhite(image.getRGB(x+1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y+1))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y-1))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y-1))){
                    size++;
                }
                if(isWhite(image.getRGB(x+1, y+1))){
                    size++;
                } 
                if(size>=5){
                    image.setRGB(x,y,white);                     
                }
            }
        }
        
        for(int y = 1; y < h-1; y++){
            int x = w - 1;
            if(isBlack(image.getRGB(x, y))){
                int size = 0;
                if(isWhite(image.getRGB(x-1, y))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y+1))){
                    size++;
                }
                if(isWhite(image.getRGB(x, y-1))){
                    size++;
                }
                //斜上下為空時,去掉此點
                if(isWhite(image.getRGB(x-1, y+1))){
                    size++;
                }
                if(isWhite(image.getRGB(x-1, y-1))){
                    size++;
                }
                if(size>=5){
                    image.setRGB(x,y,white);                     
                }
            }
        }
        
        //降噪,以1個像素點為單位
        for(int y = 1; y < h-1; y++){
            for(int x = 1; x < w-1; x++){                   
                if(isBlack(image.getRGB(x, y))){
                    int size = 0;
                    //上下左右均為空時,去掉此點
                    if(isWhite(image.getRGB(x-1, y))){
                        size++;
                    }
                    if(isWhite(image.getRGB(x+1, y))){
                        size++;
                    }
                    //上下均為空時,去掉此點
                    if(isWhite(image.getRGB(x, y+1))){
                        size++;
                    }
                    if(isWhite(image.getRGB(x, y-1))){
                        size++;
                    }
                    //斜上下為空時,去掉此點
                    if(isWhite(image.getRGB(x-1, y+1))){
                        size++;
                    }
                    if(isWhite(image.getRGB(x+1, y-1))){
                        size++;
                    }
                    if(isWhite(image.getRGB(x+1, y+1))){
                        size++;
                    } 
                    if(isWhite(image.getRGB(x-1, y-1))){
                        size++;
                    }
                    if(size>=8){
                        image.setRGB(x,y,white);                     
                    }
                }
            }
        }
        
        return image;
    }
    
     public static boolean isBlack(int colorInt)  
     {  
         Color color = new Color(colorInt);  
         if (color.getRed() + color.getGreen() + color.getBlue() <= 300)  
         {  
             return true;  
         }  
         return false;  
     }  
 
     public static boolean isWhite(int colorInt)  
     {  
         Color color = new Color(colorInt);  
         if (color.getRed() + color.getGreen() + color.getBlue() > 300)  
         {  
             return true;  
         }  
         return false;  
     }  
     
     public static int isBlack(int colorInt, int whiteThreshold) {
         final Color color = new Color(colorInt);
         if (color.getRed() + color.getGreen() + color.getBlue() <= whiteThreshold) {
             return 1;
         }
         return 0;
     }

3、切割字符串,比如識別驗證碼時,可能需要將每個字符給單獨切割下來進行單獨識別,准確率會高得多,如果驗證碼字符之間粘連度不高,可以使用最簡單的投影法,即,x軸上的每一列有多少個黑色像素點,這樣形成了一個數組,針對這個數組可以想象成一個折線圖,如果圖片沒有粘連那最理想的效果是這樣:

如果有粘連,那就是一個有波峰波谷的折線圖,只有通過算法找到波峰波谷來切割了;同理,對y軸也可以進行投影。opencv的話,還有個常用的輪廓法,需要的可以了解下(吐槽:java版的opencv和c++版本的api稍有不同,這個只有看官方文檔了)

4、裁剪,把上一步的像素點區域找到(原點+寬高)即可裁剪。

5、識別,如果字體扭曲的不是很嚴重的話,推薦直接使用tesseract-ocr簡單方便,java包好像叫tess4j,也可以自己用svm;字體扭曲嚴重的話,只有自己寫算法了,這種情況還是建議使用opencv來處理,有相應的處理方法的。

6、圖片相似度,像有些驗證碼扭曲的厲害確實破解不了的(md,難道要上神經網絡喂幾萬張圖片去識別么,拜托,我只是爬個數據而已,還是不費力氣了~~),

但是,我之前碰到過有些網站雖然破解不了,但是驗證碼圖片數量是固定的,比如裁判文書網,爬了幾千張下來才發現基本上都是重復的,之前的白弄了。其實可以直接hash的,但是為了擴展和復用,還是弄個圖片相似度,其實原理差的不多,還是圖片預處理后再進行hash,形成每張圖片獨有的指紋。

PS:簡單的可以自己處理像素,也便於學習和加深理解,更復雜的建議還是使用opencv吧~~

這里可以簡單說說我最開始怎么處理裁判文書網的驗證碼的,准確率只有60%左右(大部分8,90%多的圖片能分割出來,識別就不行了);字體,背景色,干擾線,噪點,顏色基本都不一樣,通過某些算法區分這幾個區域(分類算法很多,kmeans最簡單效果也行),然后挨着邊緣的是干擾線先去掉(需要與二值化后的進行對比確認干擾線),中間區域分類顏色最多的是字體,然后去掉其他區域,再二值化分割判斷。缺點:有些圖片干擾線和字體顏色很接近分不出來,去掉干擾線把字體的一部分也除掉了。大家還有沒有什么好的方法

 

本文轉載自:https://blog.csdn.net/wokuailewozihao/article/details/79742651


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM