1. 程式人生 > >Java識別驗證碼和影象處理

Java識別驗證碼和影象處理

閱讀本文可能會解決的問題:

① Java識別驗證碼
② Tess4J的使用和OCR識別
③ JavaCV的使用二值化和灰度測試
④ Java裁剪和縮放圖片
⑤ 如何生成數字&字母驗證碼
⑥ ...

這裡老樣子先說幾句無關的話,去年畢業的時候幫同學做了一個驗證碼識別系統,按部就班的參考了大神的操作,對於二值化這些操作都沒有深入瞭解,最近做專案遇到了要實現機器登入的操作,驗證碼自然繞不過去所以就又翻出來參考總結一下,方便自己也分享給大家。同時我也發現現在除了一些政府網站外,基本很少有采用下面我列出的這種數字&字母驗證碼了,所以針對專案需求做的這個識別可能目前並沒有什麼參考價值,但是在學習的過程中也瞭解了很多比如Tess4J和JavaCV這些技術,影象處理比如縮放和裁剪等,話不多說,下面就搞起吧。

一、常見的驗證碼識別

1.常見驗證碼

     

這裡我主要針對第四種驗證碼進行識別,其實試驗後我發現前面三個都是可以識別的,只是裁剪和二值化的閾值需要調整。

2.識別思路

輸入原始驗證碼之後,先對圖片進行干擾畫素去除,再裁剪邊角,最後利用Tess4J進行識別。

3.主要程式碼

main方法呼叫具體實現方法

    public static void main(String[] args){

        //原始驗證碼地址
        String OriginalImg = "C:\\mysoftware\\images\\upload\\OcrImg\\oi.jpg";
        //識別樣本輸出地址
        String ocrResult = "C:\\mysoftware\\images\\upload\\OcrResult\\or.jpg";
        //去噪點
        ImgUtils.removeBackground(OriginalImg, ocrResult);
        //裁剪邊角
        ImgUtils.cuttingImg(ocrResult);
        //OCR識別
        String code = Tess4J.executeTess4J(ocrResult);
        //輸出識別結果
        System.out.println("Ocr識別結果: \n" + code);

    }
其中removeBackground方法去除驗證碼噪點,首先我定義了一個臨界閾值,這個值代表畫素點的亮度,我們在實際掃描驗證碼的每一個畫素塊時通過判斷該畫素塊的亮度(獲取該畫素塊的三原色)是否超過該自定義值,從而判斷出是否刪除或保留該畫素塊,因此針對不同的驗證碼我們可以調整這個閾值,比如像我上面列出的幾種驗證碼波動一般都在100~600之間,通過測試就得出一個比較適中的閾值可以大大提高驗證碼的提純度。
public static void removeBackground(String imgUrl, String resUrl){
        //定義一個臨界閾值
        int threshold = 300;
        try{
            BufferedImage img = ImageIO.read(new File(imgUrl));
            int width = img.getWidth();
            int height = img.getHeight();
            for(int i = 1;i < width;i++){
                for (int x = 0; x < width; x++){
                    for (int y = 0; y < height; y++){
                        Color color = new Color(img.getRGB(x, y));
                        System.out.println("red:"+color.getRed()+" | green:"+color.getGreen()+" | blue:"+color.getBlue());
                        int num = color.getRed()+color.getGreen()+color.getBlue();
                        if(num >= threshold){
                            img.setRGB(x, y, Color.WHITE.getRGB());
                        }
                    }
                }
            }
            for(int i = 1;i<width;i++){
                Color color1 = new Color(img.getRGB(i, 1));
                int num1 = color1.getRed()+color1.getGreen()+color1.getBlue();
                for (int x = 0; x < width; x++)
                {
                    for (int y = 0; y < height; y++)
                    {
                        Color color = new Color(img.getRGB(x, y));

                        int num = color.getRed()+color.getGreen()+color.getBlue();
                        if(num==num1){
                            img.setRGB(x, y, Color.BLACK.getRGB());
                        }else{
                            img.setRGB(x, y, Color.WHITE.getRGB());
                        }
                    }
                }
            }
            File file = new File(resUrl);
            if (!file.exists())
            {
                File dir = file.getParentFile();
                if (!dir.exists())
                {
                    dir.mkdirs();
                }
                try
                {
                    file.createNewFile();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
            ImageIO.write(img, "jpg", file);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

這樣就能得到一個降噪之後的驗證碼,再後續通過顏色反轉將驗證碼的內容顯示出來就完成了干擾畫素的去除工作,接下來非必要的一項工作就是將驗證碼進行裁邊。為什麼要裁邊呢,通過觀察我們發現驗證碼的邊角部分沒有干擾素塗抹,在我們的去除干擾素過程中可能會影響到這部分畫素塊導致最後邊角有噪點,這時將邊角裁掉幾個畫素就可以了。

public static void cuttingImg(String imgUrl){
        try{
            File newfile=new File(imgUrl);
            BufferedImage bufferedimage=ImageIO.read(newfile);
            int width = bufferedimage.getWidth();
            int height = bufferedimage.getHeight();
            if (width > 52) {
                bufferedimage=ImgUtils.cropImage(bufferedimage,(int) ((width - 52) / 2),0,(int) (width - (width-52) / 2),(int) (height));
                if (height > 16) {
                    bufferedimage=ImgUtils.cropImage(bufferedimage,0,(int) ((height - 16) / 2),52,(int) (height - (height - 16) / 2));
                }
            }else{
                if (height > 16) {
                    bufferedimage=ImgUtils.cropImage(bufferedimage,0,(int) ((height - 16) / 2),(int) (width),(int) (height - (height - 16) / 2));
                }
            }
            ImageIO.write(bufferedimage, "jpg", new File(imgUrl));
        }catch (IOException e){
            e.printStackTrace();
        }
    }
其中裁剪圖片我們需要預先讀取寬高度,確認需要裁剪的畫素寬度,我這裡將60*20的影象寬度兩邊各裁剪4畫素,高度上下各裁剪2畫素,輸出就得到了一個清晰的影象。到這裡基本完成了對驗證碼的簡單處理,不出意外的話驗證碼由

  變為了 

到這裡就很簡單了,我們可以利用裁切將圖片平均剪成四份,再通過簡單的訓練比對完成匹配,詳細操作可以看這裡,我就不再贅述了。我這裡提供另一個思路,就是要說的Tess4J文字識別工具,通過這個工具我可以直接對當前生成的驗證碼進行OCR識別,準確率接近100%,注意我專案裡是採用Tess4J進行最終的驗證碼圖片內容識別的。

二、Tess4J的使用

1.首先需要到官網下載Tess4J的壓縮包,下載後先將解壓出來的包內tessdata檔案拷入專案根目錄,然後引入Tess4J\lib包,最後還要單獨引入Tess4J\dist\tess4j-3.4.8.jar包

2.編寫簡單的識別程式碼

public static String executeTess4J(String imgUrl){
        String ocrResult = "";
        try{
            ITesseract instance = new Tesseract();
            //instance.setLanguage("chi_sim");
            File imgDir = new File(imgUrl);
            //long startTime = System.currentTimeMillis();
            ocrResult = instance.doOCR(imgDir);
        }catch (TesseractException e){
            e.printStackTrace();
        }
        return ocrResult;
    }

ocrResult 即為最終是別的結果,程式碼相當簡單,其中我註釋掉的 instance.setLanguage("chi_sim"); 這部分是指定需要識別文字的語言,如果是識別中文按照我註釋的chi_sim填寫即可,當然這是一箇中文包,需要先在這裡找到中文包下載後放到Tess4J\tessdata目錄下即可使用。

到這裡基本就完成了對這個驗證碼的簡單識別,在這個過程中基本沒遇到什麼有難度的工作,畢竟是站在大佬們的肩膀上操作,其實還是學到了很多東西的,需求這東西說不定哪天就遇到了呢。接下來我在研究驗證碼識別的過程中瞭解到了JavaCV,比較感興趣就記錄下來,方便之後使用吧。

三、JavaCV的使用

1. 首先就是去官網下載包啦,點這裡,引入很簡單,解壓後將javacv-bin包引入專案即可(可以看到封裝了OpenCV)

2. 基本操作:獲取灰度影象、獲取二值化處理影象

public static void main(String[] args) {
        //圖片地址
        String imgUrl = "C:\\mysoftware\\images\\upload\\OcrImg\\20180607004153.png";
        //得到灰度影象
        getHuidu(imgUrl);
        //得到二值化處理影象
        getErzhihua(imgUrl);
    }

    //得到灰度影象
    public static void getHuidu(String imgUrl){
        Mat image=imread(imgUrl,CV_LOAD_IMAGE_GRAYSCALE);
        //讀入一個影象檔案並轉換為灰度影象(由無符號位元組構成)
        Mat image1=imread(imgUrl,CV_LOAD_IMAGE_COLOR);
        //讀取影象,並轉換為三通道彩色影象,這裡建立的影象中每個畫素有3位元組
        //如果輸入影象為灰度影象,這三個通道的值就是相同的
        System.out.println("image has "+image1.channels()+" channel(s)");
        //channels方法可用來檢查影象的通道數
        flip(image,image,1);//就地處理,引數1表示輸入影象,引數2表示輸出影象
        //在一視窗顯示結果
        namedWindow("輸入圖片顯示視窗");//定義視窗
        imshow("輸入圖片顯示視窗",image);//顯示視窗
        waitKey(0);//因為他是控制檯視窗,會在mian函式結束時關閉;0表示永遠的等待按鍵,正數表示等待指定的毫秒數
    }

    //得到二值化處理影象
    public static void getErzhihua(String imgUrl){
        // TODO Auto-generated method stub
        Mat image=imread(imgUrl);	//載入影象
        if(image.empty())
        {
            System.out.println("影象載入錯誤,請檢查圖片路徑!");
            return ;
        }
        imshow("原始影象",image);
        Mat gray=new Mat();
        cvtColor(image,gray,COLOR_RGB2GRAY);		//彩色影象轉為灰度影象
        imshow("灰度影象",gray);
        Mat bin=new Mat();
        threshold(gray,bin,120,255,THRESH_TOZERO); 	//影象二值化
        imshow("二值影象",bin);
        waitKey(0);
    }

看一下效果:

四、生成驗證碼

生成驗證碼大致思路:載入登入介面時向後臺請求一個隨機驗證碼code存入session,並在頁面以圖形顯示出來,使用者輸入登入資訊後點擊登入,後臺讀出session的驗證碼code比對是否一致,不一致返回錯誤資訊,一致則進行登入賬號密碼校驗。
1. 首先在login的頁面新增一個img
<div class="form-group">
                   <label class="col-sm-2 control-label">驗證</label>
                   <div class="col-sm-10">
                       <input id = "code" type="text" name="vcode" value="請輸入驗證碼" maxlength="4" tabindex="3" style="width:123px;padding: 6px 12px;border: 1px solid #ccc;font-size: 14px;line-height: 1.42857143;color: #555;background-color: #fff;border-radius: 4px;box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;" onfocus="if(this.value=='請輸入驗證碼'){this.value='';}"  onblur="if(this.value==''){this.value='請輸入驗證碼';}"/>
                       <a href="#" id="js-get_mobile_vcode" class="button btn-disabled">
                           <img src="../recordHome/getRand" id="randImg"  onclick="changeValidateCode(this)"/>
                       </a>
                   </div>
               </div>
其中為了方便使用者使用,需要做點選圖片自動獲取新的驗證碼,所以changeValidateCode()方法是動態去後臺請求驗證碼並設定到頁面上
function changeValidateCode() {
    var timestamp = new Date().getTime();
    $("#randImg").attr('src','../recordHome/getRand?flag='+timestamp);
}
請求時新增時間戳是為了防止瀏覽器快取導致獲取失敗
2. 後臺生成驗證碼
/**
	 * 隨機生成4位驗證碼
	 * @param httpSession
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/getRand")
	public void rand(HttpSession httpSession, HttpServletRequest request, HttpServletResponse response){
		// 在記憶體中建立圖象
		int width = 65, height = 20;
		BufferedImage image = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
		// 獲取圖形上下文
		Graphics g = image.getGraphics();
		// 生成隨機類
		Random random = new Random();
		// 設定背景色
		g.setColor(getRandColor(200, 250));
		g.fillRect(0, 0, width, height);
		// 設定字型
		g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
		// 隨機產生155條幹擾線,使圖象中的認證碼不易被其它程式探測到
		g.setColor(getRandColor(160, 200));
		for (int i = 0; i < 155; i++) {
			int x = random.nextInt(width);
			int y = random.nextInt(height);
			int xl = random.nextInt(12);
			int yl = random.nextInt(12);
			g.drawLine(x, y, x + xl, y + yl);
		}
		// 取隨機產生的認證碼(6位數字)
		String sRand = "";
		for (int i = 0; i < 4; i++) {
			String rand = String.valueOf(random.nextInt(10));
			sRand += rand;
			// 將認證碼顯示到圖象中
			g.setColor(new Color(20 + random.nextInt(110), 20 + random
					.nextInt(110), 20 + random.nextInt(110)));
			// 呼叫函數出來的顏色相同,可能是因為種子太接近,所以只能直接生成
			g.drawString(rand, 13 * i + 6, 16);
		}
		// 將認證碼存入SESSION
		ContextHolderUtils.getSession().setAttribute("rand", sRand);
		//httpSession.setAttribute("rand", sRand);
		// 圖象生效
		g.dispose();
		try{
			ImageIO.write(image, "JPEG", response.getOutputStream());
			response.getOutputStream().flush();
		}catch (Exception e){
		}
	}

	/**
	 * 給定範圍獲得隨機顏色
	 * @param fc
	 * @param bc
	 * @return
	 */
	private Color getRandColor(int fc, int bc) {
		Random random = new Random();
		if (fc > 255)
			fc = 255;
		if (bc > 255)
			bc = 255;
		int r = fc + random.nextInt(bc - fc);
		int g = fc + random.nextInt(bc - fc);
		int b = fc + random.nextInt(bc - fc);
		return new Color(r, g, b);
	}
使用ContextHolderUtils.getSession().setAttribute("rand", sRand);將驗證碼存入session中
@RequestMapping("/login")
	public String login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password, @RequestParam String vcode) {
		if(!vcode.equals(ContextHolderUtils.getSession().getAttribute("rand"))){
			request.setAttribute("failMsg", "驗證碼不正確!");
			return "/base/login";
		}
		logger.info("使用者登入使用者:" + username );
		...
	}
登入時進行校驗,就醬so easy

最後是全部程式碼,這裡說到的我都整合到了專案裡,直接下載就可以執行,歡迎大家指正。

有用的話可以☆star一下哦