java實現識別二維碼圖片功能方法詳解與實例源碼
更新時間:2022年12月10日 09:07:26 作者:啊猿
這篇文章主要介紹了java實現識別二維碼圖片,java無法識別二維碼情況下對二維碼圖片調優(yōu)功能方法與實例源碼,需要的朋友可以參考下
首先添加依賴
<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");
//設置優(yōu)化精度
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二維碼圖片調優(yōu)
如果上述不能識別的話,那么就需要對圖片處理一次,然后再進行識別,這里是個調優(yōu)圖片的工具類。
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個點都是黑,則該點腐蝕操作后為黑
* 由于公司圖片態(tài)模糊,完全達到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;
// 設置截取規(guī)則
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實現識別二維碼圖片功能文章請查看下面的相關鏈接
相關文章
springboot 使用yml配置文件給靜態(tài)變量賦值教程
這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
springboot集成本地緩存Caffeine的三種使用方式(小結)
本文主要介紹了springboot集成本地緩存Caffeine的三種使用方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-06-06
Java實現監(jiān)控多個線程狀態(tài)的簡單實例
下面小編就為大家?guī)硪黄狫ava實現監(jiān)控多個線程狀態(tài)的簡單實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
Java Websocket Canvas實現井字棋網絡游戲
這篇文章主要介紹了Java Websocket Canvas實現井字棋網絡游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
SpringCloud Gateway的熔斷限流配置實現方法
Spring Cloud Gateway支持通過配置熔斷和限流機制來保證服務的穩(wěn)定性和可用性,可通過Hystrix和Resilience4j兩種方式實現。Hystrix通過注解和配置文件實現熔斷限流,Resilience4j通過編程式配置實現2023-04-04

