opencv本身有二维码识别功能,但是识别效果不是很好,它对二维码图片要求较高,虽然可以有图片处理,甚至抠出二维码区域,但是整体识别与微信二维码识别功能还是有差距的。
微信二维码识别,采用了机器学习算法,加入了CNN模型的概念,它作为三方库,开源给了opencv,而且支持java语言,不过要使用,需要结合操作系统做编译,需要下载opencv源码,还需要下载opencv_contrib代码,这里面就是一些三方库,微信二维码就在这里面。
源码编译需要生成opencv_java45x.dll,opencv-java45x.so,opencv_java45x.jar等文件,不仅需要动态库,还需要jar包。
在windows系统下,动态库是dll类型,在linux下动态库就是so类型。如果你能下载到opencv-4.x版本的jar,dll,so文件,可以直接使用,如果没有,就需要手动编译(编译过程非常复杂,建议不要轻易尝试)。
有了jar,动态库,就很方便了,无需很复杂的设置,做过java开发的肯定知道jar需要加入类路径下,动态库只需要指定一个位置,java能像读文件一样加载到就可以了。
这里以windows系统为例,所需的文件就是opencv-453.jar和opencv_java453.dll动态库,如果是maven项目,我们可以将opencv-453.jar作为本地jar加入项目中。
<dependency>
<groupId>org</groupId>
<artifactId>opencv</artifactId>
<version>453</version>
<scope>system</scope>
<systemPath>${project.basedir}\src\main\resources\opencvlib\opencv-453.jar</systemPath>
</dependency>
java代码如下:
package org.example;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.wechat_qrcode.WeChatQRCode;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.net.URL;
import java.util.List;
public class WeChatQRCodeTool {
private static volatile WeChatQRCodeTool instance;
private static volatile WeChatQRCode detector;
private WeChatQRCodeTool(){
System.setProperty("java.awt.headless","true");
URL url = null;
String os = System.getProperty("os.name");
if(os.startsWith("Linux")){
url = ClassLoader.getSystemResource("opencvlib/libopencv_java452.so");
System.load(url.getPath());
detector = new org.opencv.wechat_qrcode.WeChatQRCode();
}else{
url = ClassLoader.getSystemResource("opencvlib/opencv_java452.dll");
System.load(url.getPath());
ClassLoader cl = WeChatQRCodeTool.class.getClassLoader();
URL detectprototxt = cl.getResource("opencvlib/detect.prototxt");
URL detectcaffemodel =cl.getResource("opencvlib/detect.caffemodel");
URL srprototxt=cl.getResource("opencvlib/sr.prototxt");
URL srcaffemodel =cl.getResource("opencvlib/sr.caffemodel");
detector = new org.opencv.wechat_qrcode.WeChatQRCode(
detectprototxt.getPath().substring(1),
detectcaffemodel.getPath().substring(1),
srprototxt.getPath().substring(1),
srcaffemodel.getPath().substring(1));
}
}
public static WeChatQRCodeTool getInstance(){
if(instance==null){
synchronized (WeChatQRCodeTool.class){
if(instance==null){
instance = new WeChatQRCodeTool();
}
}
}
return instance;
}
public static Mat bufImg2Mat(BufferedImage original, int imgType, int matType) {
if (original == null) {
throw new IllegalArgumentException("original == null");
}
byte[] pixels = null;
// Don't convert if it already has correct type
if (original.getType() != imgType) {
// Create a buffered image
BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
// Draw the image onto the new buffer
Graphics2D g = image.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(original, 0, 0, null);
pixels = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
} finally {
g.dispose();
}
} else {
pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
}
Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
mat.put(0, 0, pixels);
return mat;
}
public String decode(BufferedImage srcImage) {
int cvtype = CvType.CV_8UC3;
if(srcImage.getType() == BufferedImage.TYPE_BYTE_GRAY) {
cvtype = CvType.CV_8UC1;
}
Mat image = bufImg2Mat(srcImage, srcImage.getType(), cvtype);
List<String> result2 = detector.detectAndDecode(image);
if (result2 != null && result2.size() > 0) {
return result2.get(0);
}
return null;
}
public String decodeQRCodeByPath(String qrCodePath){
String qrCodeText = null;
try {
BufferedImage image = ImageIO.read(new File(qrCodePath));
qrCodeText = decode(image);
}catch (Exception e){
e.printStackTrace();
}
return qrCodeText;
}
public static void main(String[] args) {
String img = "C:\\Users\\86159\\Pictures\\qr-3.jpg";
String result = getInstance().decodeQRCodeByPath(img);
System.out.println(result);
}
}
示例图片如下:
这个二维码图片,在一个大图中,人眼可以看,但是普通的二维码识别基本不可能识别,但是使用微信二维码识别,没有任何问题。识别结果如下所示:
本人尝试过很多二维码图片,使用微信识别,基本一试一个准,果断放弃com.google.zxing提供的二维码识别功能。
不得不佩服微信二维码识别功能的强大,再回过头来想想,这里面使用了深度学习的内容,比如CNN模型,虽然不懂,感觉已经很厉害了。
本人在尝试过程中,发现,很多文章提到,Java使用微信二维码识别功能,需要下载模型文件,也就是代码中提到的:detect.prototxt,detect.caffemodel,sr.prototxt,sr.caffemodel文件,其实,不用这几个文件,也是可以的。
URL detectprototxt = cl.getResource("opencvlib/detect.prototxt");
URL detectcaffemodel =cl.getResource("opencvlib/detect.caffemodel");
URL srprototxt=cl.getResource("opencvlib/sr.prototxt");
URL srcaffemodel =cl.getResource("opencvlib/sr.caffemodel");
/*
detector = new org.opencv.wechat_qrcode.WeChatQRCode(
detectprototxt.getPath().substring(1),
detectcaffemodel.getPath().substring(1),
srprototxt.getPath().substring(1),
srcaffemodel.getPath().substring(1));*/
detector = new org.opencv.wechat_qrcode.WeChatQRCode();
本文写到这里,其实也差不多了,但是这种识别只能在windows下,java开发,尤其是服务端,最后的代码99%都是部署在linux服务器下的,那么,本文介绍的办法无需改动,但是linux环境要求比较特殊。不仅需要opencv-453.jar,opencv_java453.so 文件,动态库在调用的时候,还需要一些其他的opencv依赖,这么一来,还是需要编译opencv+opencv_contrib。
我在尝试部署到linux下发现,如果一个机器编译了so文件,可以复制到其他机器使用,其他机器就不需要编译了,但是这些动态库太多了,还是很麻烦的。