java實現識別二維碼圖片功能方法詳解與實例源碼
首先添加依賴
<dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.3</version> </dependency>
java識別二維碼代碼實現
import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import sun.misc.BASE64Decoder; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 作用:二維碼識別(圖片) * 類名:QRCodeUtils **/ public class QRCodeUtils { /** * 解析二維碼,此方法解析一個路徑的二維碼圖片 * path:圖片路徑 */ public static String deEncodeByPath(String path) { String content = null; BufferedImage image; try { image = ImageIO.read(new File(path)); LuminanceSource source = new BufferedImageLuminanceSource(image); Binarizer binarizer = new HybridBinarizer(source); BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer); Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>(); hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解碼 System.out.println("圖片中內容: "); System.out.println("content: " + result.getText()); content = result.getText(); } catch (IOException e) { e.printStackTrace(); } catch (NotFoundException e) { //這裡判斷如果識別不瞭帶LOGO的圖片,重新添加上一個屬性 try { image = ImageIO.read(new File(path)); LuminanceSource source = new BufferedImageLuminanceSource(image); Binarizer binarizer = new HybridBinarizer(source); BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer); Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>(); //設置編碼格式 hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); //設置優化精度 hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); //設置復雜模式開啟(我使用這種方式就可以識別微信的二維碼瞭) hints.put(DecodeHintType.PURE_BARCODE,Boolean.TYPE); Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解碼 System.out.println("圖片中內容: "); System.out.println("content: " + result.getText()); content = result.getText(); } catch (IOException e) { e.printStackTrace(); } catch (NotFoundException e) { e.printStackTrace(); } } return content; } }
測試:
public static void main(String [] args){ deEncodeByPath("D:\\Users/admin/Desktop/erweima/timg (5).jpg");//二維碼圖片路徑 }
Java二維碼圖片調優
如果上述不能識別的話,那麼就需要對圖片處理一次,然後再進行識別,這裡是個調優圖片的工具類。
package com.face.ele.common.utils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * @author weijianxing * @description: TODO * @date 2020/11/26 9:28 */ public class ImageOptimizationUtil { // 閾值0-255 public static int YZ = 150; /** * 圖像二值化處理 * * @param filePath 要處理的圖片路徑 * @param fileOutputPath 處理後的圖片輸出路徑 */ public static void binarization(String filePath, String fileOutputPath) throws IOException { File file = new File(filePath); BufferedImage bi = ImageIO.read(file); // 獲取當前圖片的高,寬,ARGB int h = bi.getHeight(); int w = bi.getWidth(); int arr[][] = new int[w][h]; // 獲取圖片每一像素點的灰度值 for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { // getRGB()返回默認的RGB顏色模型(十進制) arr[i][j] = getImageGray(bi.getRGB(i, j));// 該點的灰度值 } } // 構造一個類型為預定義圖像類型,BufferedImage BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY); // 和預先設置的閾值大小進行比較,大的就顯示為255即白色,小的就顯示為0即黑色 for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { if (getGray(arr, i, j, w, h) > YZ) { int white = new Color(255, 255, 255).getRGB(); bufferedImage.setRGB(i, j, white); } else { int black = new Color(0, 0, 0).getRGB(); bufferedImage.setRGB(i, j, black); } } } ImageIO.write(bufferedImage, "jpg", new File(fileOutputPath)); } /** * 圖像的灰度處理 * 利用浮點算法:Gray = R*0.3 + G*0.59 + B*0.11; * * @param rgb 該點的RGB值 * @return 返回處理後的灰度值 */ private static int getImageGray(int rgb) { String argb = Integer.toHexString(rgb);// 將十進制的顏色值轉為十六進制 // argb分別代表透明,紅,綠,藍 分別占16進制2位 int r = Integer.parseInt(argb.substring(2, 4), 16);// 後面參數為使用進制 int g = Integer.parseInt(argb.substring(4, 6), 16); int b = Integer.parseInt(argb.substring(6, 8), 16); int gray = (int) (r*0.28 + g*0.95 + b*0.11); return gray; } /** * 自己加周圍8個灰度值再除以9,算出其相對灰度值 * * @param gray * @param x 要計算灰度的點的橫坐標 * @param y 要計算灰度的點的縱坐標 * @param w 圖像的寬度 * @param h 圖像的高度 * @return */ public static int getGray(int gray[][], int x, int y, int w, int h) { int rs = gray[x][y] + (x == 0 ? 255 : gray[x - 1][y]) + (x == 0 || y == 0 ? 255 : gray[x - 1][y - 1]) + (x == 0 || y == h - 1 ? 255 : gray[x - 1][y + 1]) + (y == 0 ? 255 : gray[x][y - 1]) + (y == h - 1 ? 255 : gray[x][y + 1]) + (x == w - 1 ? 255 : gray[x + 1][y]) + (x == w - 1 || y == 0 ? 255 : gray[x + 1][y - 1]) + (x == w - 1 || y == h - 1 ? 255 : gray[x + 1][y + 1]); return rs / 9; } /** * 二值化後的圖像的開運算:先腐蝕再膨脹(用於去除圖像的小黑點) * * @param filePath 要處理的圖片路徑 * @param fileOutputPath 處理後的圖片輸出路徑 * @throws IOException */ public static void opening(String filePath, String fileOutputPath) throws IOException { File file = new File(filePath); BufferedImage bi = ImageIO.read(file); // 獲取當前圖片的高,寬,ARGB int h = bi.getHeight(); int w = bi.getWidth(); int arr[][] = new int[w][h]; // 獲取圖片每一像素點的灰度值 for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { // getRGB()返回默認的RGB顏色模型(十進制) arr[i][j] = getImageGray(bi.getRGB(i, j));// 該點的灰度值 } } int black = new Color(0, 0, 0).getRGB(); int white = new Color(255, 255, 255).getRGB(); BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY); // 臨時存儲腐蝕後的各個點的亮度 int temp[][] = new int[w][h]; // 1.先進行腐蝕操作 for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { /* * 為0表示改點和周圍8個點都是黑,則該點腐蝕操作後為黑 * 由於公司圖片態模糊,完全達到9個點全為黑的點太少,最後效果很差,故改為瞭小於30 * (寫30的原因是,當隻有一個點為白,即總共255,調用getGray方法後得到255/9 = 28) */ if (getGray(arr, i, j, w, h) < 30) { temp[i][j] = 0; } else{ temp[i][j] = 255; } } } // 2.再進行膨脹操作 for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { bufferedImage.setRGB(i, j, white); } } for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { // 為0表示改點和周圍8個點都是黑,則該點腐蝕操作後為黑 if (temp[i][j] == 0) { bufferedImage.setRGB(i, j, black); if(i > 0) { bufferedImage.setRGB(i-1, j, black); } if (j > 0) { bufferedImage.setRGB(i, j-1, black); } if (i > 0 && j > 0) { bufferedImage.setRGB(i-1, j-1, black); } if (j < h-1) { bufferedImage.setRGB(i, j+1, black); } if (i < w-1) { bufferedImage.setRGB(i+1, j, black); } if (i < w-1 && j > 0) { bufferedImage.setRGB(i+1, j-1, black); } if (i < w-1 && j < h-1) { bufferedImage.setRGB(i+1, j+1, black); } if (i > 0 && j < h-1) { bufferedImage.setRGB(i-1, j+1, black); } } } } ImageIO.write(bufferedImage, "jpg", new File(fileOutputPath)); } public static void main(String[] args) { String fullPath="E:\\weijianxing\\img\\微信圖片_20201202160240.jpg"; String newPath="E:\\weijianxing\\img\\1new_微信圖片_20201202160240.jpg"; try { ImageOptimizationUtil.binarization(fullPath,newPath); } catch (IOException e) { e.printStackTrace(); } } }
可以手動測試,然後對改代碼的部分進行調正對應的參數– gray變量裡的計算進行灰度調整
private static int getImageGray(int rgb) { String argb = Integer.toHexString(rgb);// 將十進制的顏色值轉為十六進制 // argb分別代表透明,紅,綠,藍 分別占16進制2位 int r = Integer.parseInt(argb.substring(2, 4), 16);// 後面參數為使用進制 int g = Integer.parseInt(argb.substring(4, 6), 16); int b = Integer.parseInt(argb.substring(6, 8), 16); int gray = (int) (r*0.28 + g*0.95 + b*0.11); return gray; }
第二種方法:
package com.ghl.magicbox.qrcode.b; import cn.hutool.core.util.IdUtil; import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.opencv.core.*; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.CLAHE; import org.opencv.imgproc.Imgproc; import org.springframework.util.ResourceUtils; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @Author: GHL * @Date: 2022/2/18 * @Description: */ @Slf4j public class QRCodeUtil { /** * 默認放大倍數 */ private final static int TIMES = 4; static { // 加載Opencv的dll文件 URL url = ClassLoader.getSystemResource("lib/opencv_java3416.dll"); System.load(url.getPath()); } /** * 復雜圖片二維碼解析 * * @param file * @return */ public static String complexDecode(File file) { String tempFilePath = null; try { log.debug("QRCodeUtil -> complexDecode() fileName:{}",file.getName()); tempFilePath = getFilePath(file.getName()); //第一次解析:直接解析 log.debug("QRCodeUtil -> complexDecode() firstDecode begin by:{}",file.getName()); String codeDataByFirst = simpleDecode(file); if (codeDataByFirst != null) { return codeDataByFirst; } //第二次解析:定位圖中二維碼,截圖放大 log.debug("QRCodeUtil -> complexDecode() secondDecode begin by:{}",file.getName()); piz(file.getAbsolutePath(),tempFilePath); String codeDataBySecond = simpleDecode(tempFilePath); if (codeDataBySecond != null) { return codeDataBySecond; } //第三次解析:將截圖後二維碼二值化 log.debug("QRCodeUtil -> complexDecode() thirdDecode begin by:{}",file.getName()); Mat mat = binarization(tempFilePath); String codeDataByThird = simpleDecode(tempFilePath); if (codeDataByThird != null) { return codeDataByThird; } //第四次解析: 進行限制對比度的自適應直方圖均衡化處理 log.debug("QRCodeUtil -> complexDecode() fourthDecode begin by:{}",file.getName()); limitContrast(tempFilePath,mat); String codeDataByFourth = simpleDecode(tempFilePath); if (codeDataByFourth != null) { log.debug("QRCodeUtil -> complexDecode() fileName:{} state:{} result:{}",file.getName(),Boolean.TRUE,codeDataByFourth); return codeDataByFourth; } log.debug("QRCodeUtil -> complexDecode() fileName:{} state:{}",file.getName(), Boolean.FALSE); } finally { file.deleteOnExit(); if (tempFilePath != null){ file = new File(tempFilePath); file.deleteOnExit(); } } return null; } /** * 復雜圖片二維碼解析 * * @param path * @return */ public static String complexDecode(String path) { return complexDecode(new File(path)); } /** * 復雜圖片二維碼解析 * * @param originalFile * @return */ public static String complexDecode(MultipartFile originalFile) { String filePath = getFilePath(originalFile.getOriginalFilename()); File mkFile = new File(filePath); if (!mkFile.exists()){ mkFile.mkdir(); log.debug("QRCodeUtil -> complexDecode() create temp file ready by:{}",originalFile.getOriginalFilename()); } try { originalFile.transferTo(mkFile); } catch (IOException e) { e.printStackTrace(); } return complexDecode(mkFile); } /** * 簡單二維碼解析 * * @param path * @return */ public static String simpleDecode(String path) { return simpleDecode(new File(path)); } /** * 簡單二維碼解析 * * @param file * @return zxing解析率實測與opencv差不多。所以直接使用zxing解析 * zxing版本高能提高識別率 */ public static String simpleDecode(File file) { try { BufferedImage image = ImageIO.read(file); LuminanceSource source = new BufferedImageLuminanceSource(image); Binarizer binarizer = new HybridBinarizer(source); BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer); Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>(); hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); Result result = new MultiFormatReader().decode(binaryBitmap, hints); return result.getText(); } catch (Exception e) { return null; } } /** * 獲取臨時文件存儲地址 */ @SneakyThrows private static String getFilePath(String fileName) { String path = ResourceUtils.getFile("classpath:").getPath() + "/static/decodeWork/"; File folder = new File(path); if (!folder.exists()){ folder.mkdirs(); } String contentType = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1) : null; String newFileName = IdUtil.getSnowflake(0, 0).nextId() + "." + contentType; return path + newFileName; } /** * 定位 - > 截取 -> 放大 * @param filePath * @param tempFilePath */ private static void piz(String filePath, String tempFilePath) { Mat srcGray = new Mat(); Mat src = Imgcodecs.imread(filePath, 1); List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); List<MatOfPoint> markContours = new ArrayList<MatOfPoint>(); //System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // URL url = ClassLoader.getSystemResource("lib/opencv_java3416.dll"); // System.load(url.getPath()); //圖片太小就放大 if (src.width() * src.height() < 90000) { Imgproc.resize(src, src, new Size(800, 600)); } // 彩色圖轉灰度圖 Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_RGB2GRAY); // 對圖像進行平滑處理 Imgproc.GaussianBlur(srcGray, srcGray, new Size(3, 3), 0); Imgproc.Canny(srcGray, srcGray, 112, 255); Mat hierarchy = new Mat(); Imgproc.findContours(srcGray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE); for (int i = 0; i < contours.size(); i++) { MatOfPoint2f newMtx = new MatOfPoint2f(contours.get(i).toArray()); RotatedRect rotRect = Imgproc.minAreaRect(newMtx); double w = rotRect.size.width; double h = rotRect.size.height; double rate = Math.max(w, h) / Math.min(w, h); // 長短軸比小於1.3,總面積大於60 if (rate < 1.3 && w < srcGray.cols() / 4 && h < srcGray.rows() / 4 && Imgproc.contourArea(contours.get(i)) > 60) { // 計算層數,二維碼角框有五層輪廓(有說六層),這裡不計自己這一層,有4個以上子輪廓則標記這一點 double[] ds = hierarchy.get(0, i); if (ds != null && ds.length > 3) { int count = 0; if (ds[3] == -1) { //最外層輪廓排除 continue; } // 計算所有子輪廓數量 while ((int) ds[2] != -1) { ++count; ds = hierarchy.get(0, (int) ds[2]); } if (count >= 4) { markContours.add(contours.get(i)); } } } } /* * 二維碼有三個角輪廓,正常需要定位三個角才能確定坐標,本工具當識別到兩個點的時候也將二維碼定位出來; * 當識別到兩個及兩個以上點時,取兩個點中間點,往四周擴散截取 當小於兩個點時,直接返回 */ if (markContours.size() == 0) { return; } else if (markContours.size() == 1) { capture(markContours.get(0), src ,tempFilePath); } else { List<MatOfPoint> threePointList = new ArrayList<>(); threePointList.add(markContours.get(0)); threePointList.add(markContours.get(1)); capture(threePointList, src,tempFilePath); } } /** * 當隻識別到二維碼的兩個定位點時,根據兩個點的中點進行定位 * @param threePointList * @param src */ private static void capture(List<MatOfPoint> threePointList, Mat src, String tempFilePath) { Point p1 = centerCal(threePointList.get(0)); Point p2 = centerCal(threePointList.get(1)); Point centerPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); double width = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + 50; // 設置截取規則 Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0, (int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width), (int) (2 * width)); // 截取二維碼 Mat dstRoi = new Mat(src, roiArea); // 放大圖片 Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width)); Imgcodecs.imwrite(tempFilePath, dstRoi); } /** * 針對對比度不高的圖片,隻能識別到一個角的,直接以該點為中心截取 * @param matOfPoint * @param src * @param tempFilePath */ private static void capture(MatOfPoint matOfPoint, Mat src, String tempFilePath) { Point centerPoint = centerCal(matOfPoint); int width = 200; Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0, (int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width), (int) (2 * width)); // 截取二維碼 Mat dstRoi = new Mat(src, roiArea); // 放大圖片 Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width)); Imgcodecs.imwrite(tempFilePath, dstRoi); } /** * 獲取輪廓的中心坐標 * @param matOfPoint * @return */ private static Point centerCal(MatOfPoint matOfPoint) { double centerx = 0, centery = 0; MatOfPoint2f mat2f = new MatOfPoint2f(matOfPoint.toArray()); RotatedRect rect = Imgproc.minAreaRect(mat2f); Point vertices[] = new Point[4]; rect.points(vertices); centerx = ((vertices[0].x + vertices[1].x) / 2 + (vertices[2].x + vertices[3].x) / 2) / 2; centery = ((vertices[0].y + vertices[1].y) / 2 + (vertices[2].y + vertices[3].y) / 2) / 2; Point point = new Point(centerx, centery); return point; } /** * 二值化圖像 * @param filePath 圖像地址 */ private static Mat binarization(String filePath){ Mat mat = Imgcodecs.imread(filePath, 1); // 彩色圖轉灰度圖 Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY); // 對圖像進行平滑處理 Imgproc.blur(mat, mat, new Size(3, 3)); // 中值去噪 Imgproc.medianBlur(mat, mat, 5); // 這裡定義一個新的Mat對象,主要是為瞭保留原圖,未下次處理做準備 Mat mat2 = new Mat(); // 根據OTSU算法進行二值化 Imgproc.threshold(mat, mat2, 205, 255, Imgproc.THRESH_OTSU); // 生成二值化後的圖像 Imgcodecs.imwrite(filePath, mat2); return mat; } /** * 圖像進行限制對比度的自適應直方圖均衡化處理 * @param filePath */ public static void limitContrast(String filePath,Mat mat){ CLAHE clahe = Imgproc.createCLAHE(2, new Size(8, 8)); clahe.apply(mat, mat); Imgcodecs.imwrite(filePath, mat); } public static void main(String[] args) { String s = complexDecode("C:\\Users\\ghl\\Desktop\\b.jpg"); System.out.println(s); } }
更多關於java實現識別二維碼圖片功能文章請查看下面的相關鏈接
推薦閱讀:
- Java數字圖像處理之圖像灰度處理
- java使用字符畫一個海綿寶寶
- 25行Java代碼將普通圖片轉換為字符畫圖片和文本的實現
- java實現圖片反色處理示例
- 使用java + OpenCV破解頂象面積驗證碼的示例