第二弹链接

https://blog.csdn.net/StrangeSir/article/details/93143177

背景

java开发,公司最近需要在对凭证识别的过程中加入二维码识别,在网上搜索后,选定了两款开源工具,分别是谷歌的ZXING,以及QRcode,但本地测试后发现,QRcode相对于ZXING效果还是要差一点。但ZXING主要还是针对移动端开发多点,网上大部分用ZXING的都是android,优化相关的也都是Android相关的,诸如ZXING和ZBAR结合使用(吐槽,ZBAR好像java用不了,或者因为我太水了不会用,不能像ZXING那样直接引jar使用),使用某某图像处理软件,先处理,但我查了下该软件,同样不是很好引入。但经过一段时间的研究深入发现,有两个方法还是有一定的效果,截取放大和图像二值化处理。

1.截取放大(效果相对较弱)

该方法是最初尝试的一种方法,效果还是有的,毕竟想想微信在扫一扫的时候都会自动放大镜头。初步实现如下,大致意思是将图片横竖切两刀,一共分成4张,再等比放大为原来的4倍(也就是原图大小),放大4倍的原因是,图像过大好像识别不了,最初试过直接放大原图,但根据本人测试,图片并不是越大越好,有一个中间值能识别到,过大过小都会失败,所有最后放弃了该方法。有兴趣的朋友可以看看临界值是怎么获取的,另外切分法也是因为不知道如何初步定位二维码位置再进行精准切割,只能用很low的等比切割。具体代码就不写了,当时也删了,用的是JDK自带的API,可以直接百度到。

2.图像二值化(效果不错,但针对像素不高的没有测试,我们公司的是像素比较高但二维码特别小)

关于图像二值化是什么,大家可百度自行脑部,简单来说,就是将每个像素点有原来的0-255变为只有两种,要么黑,要么白。为啥能有效果的原因是,在我们肉眼看来,某些二维码很清楚,但实际上二维码中间的缝隙有很对朦朦胧胧的小点在干扰识别,所有设置一个阈值,将中间的点去除掉就好,直接上代码(注:该代码也是其它网友提供的,忘了地址,当时拿去就用了,阈值我直接用的180,没有去不断增加阈值去试探,大家根据自己的实际情况,看是设置定制还是需要在某个区间增加阈值去反复识别。

3.图像开运算

该思想同样也是那位网友提供,理论上效果是有的,不过人家不是纯java,么有实现代码。最近有空闲时间,研究了下开运算的原理,简单的做了给实现,但不是针对彩色图片的,写的是针对经过二值化后的图片,其实道理都差不多,如果需要用这个方法,还是要看看我的代码,适当的地方标记了需要修改。

4.图像的灰化

感觉效果和二值化类似,也写了给实现,不过主要是用来给二值化做辅助用的,单纯的灰化转换没有写,要改也很简单

代码实现如下,备注也比较详细,有问题可以留言

package com.demo;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageUtils {

// 阈值0-255
public static int YZ = 150;

/**
 * 图像二值化处理
 * 
 * @param filePath 要处理的图片路径
 * @param fileOutputPath 处理后的图片输出路径
 * @throws IOException
 */
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.3 + g*0.59 + 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));
}

}

总结

其实总体思路就是在进行识别前先进行图像处理,这都是常用的图像处理方法,大家若有更有效的方法,可以先看看原理,再自己实现下,其实也很简答,要怪就怪JAVA在图像处理这块太弱鸡了

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐