想做图片识别,但是大厂只提供接口,看不到实际代码,只能自己来动手了.这篇文章主要是介绍如何从一张图片里面提取色彩边界,用于后面做特征分析,算是基础部分.
引入jar包:
<dependency>
<groupId>net.coobirdgroupId>
<artifactId>thumbnailatorartifactId>
<version>0.4.12version>
dependency>
实现代码如下:
import net.coobird.thumbnailator.Thumbnails;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ImageUtil {
// 获取图片线条边界用
// 阈值越大,空白越多
// 值3000000适用于色彩分明的图片,如卡通人物蜡笔小新
// 值1000000适用于色彩过渡平缓的图片,如3D卡通今年我们十七八岁
private static final int threshold = 1000000;
// 生成文本宽度
// 值100适用于商标,头像等小图片
// 值400适用于复杂,内容丰富的大图片
private static final int picWidth = 400;
public static void main(String[] args) throws IOException {
String fromPic = "C:\\Users\\Public\\Pictures\\Sample Pictures\\241707553465767.png";
String toTxt = "d:\\test5.txt";
BufferedImage bufferedImage = ImageIO.read(new File(fromPic));
// 1.压缩图片
BufferedImage compactImage = Thumbnails.of(bufferedImage).size(picWidth, 1000).asBufferedImage();
// 2.灰度化
BufferedImage grayImage = grayingImage(compactImage);
// 3.二值化
//BufferedImage binaryImage = binaryImage(grayImage);
// 4获取边界
BufferedImage borderImage = getImageBorder(grayImage);
// 5.输出到txt文本
writeToTxt(borderImage, toTxt);
// 6.保存图片
File newFile = new File("d:\\test8.jpg");
ImageIO.write(borderImage, "jpg", newFile);
}
/**
* 灰度化图片
* @param bufferedImage 原图片
* @return 灰度化之后的图片
*/
private static BufferedImage grayingImage(BufferedImage bufferedImage) {
BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
for (int i = 0; i < bufferedImage.getWidth(); i++) {
for (int j = 0; j < bufferedImage.getHeight(); j++) {
int color = bufferedImage.getRGB(i, j);
grayImage.setRGB(i, j, color);
}
}
return grayImage;
}
/**
* 二值化图片
* @param bufferedImage 原图片
* @return 二值化后的图片
*/
private static BufferedImage binaryImage(BufferedImage bufferedImage) {
BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType());
int threshold = getMeanThreshold(bufferedImage);
for (int i = 0; i < bufferedImage.getWidth(); i++) {
for (int j = 0; j < bufferedImage.getHeight(); j++) {
int color = bufferedImage.getRGB(i, j);
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
if (gray > threshold) {
// 白色
grayImage.setRGB(i, j, 0xFFFFFF);
}
else {
// 黑色
grayImage.setRGB(i, j, 0);
}
}
}
return grayImage;
}
/**
* 获取图片的阀值,采用基于灰度平均值的阈值
* @param bufferedImage 原图片
* @return 二值化的阈值
*/
private static int getMeanThreshold(BufferedImage bufferedImage) {
int w = bufferedImage.getWidth();
int h = bufferedImage.getHeight();
int num = 0;
int sum = 0;
for(int i=0; i<w; i++) {
for(int j = 0; j < h; j++) {
int color = bufferedImage.getRGB(i, j);
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
sum += gray;
num += 1;
}
}
// 测试表明,阀值取平均值的1.2倍效果最好。
int threshold = sum / num;
if(threshold * 1.2 < 255) {
threshold = (int)(1.2 * sum / num);
}
System.out.println("width: " + w + " height: " + h + " threshold: " + threshold);
return threshold;
}
/**
* 输出 0,1 TXT文本
*/
public static void writeToTxt(BufferedImage bufferedImage, String toSaveFilePath) {
File file = new File(toSaveFilePath);
try {
Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8);
StringBuilder builder = new StringBuilder();
for (int j = 0; j < bufferedImage.getHeight(); j++) {
for(int i = 0; i < bufferedImage.getWidth(); i++) {
int color = bufferedImage.getRGB(i, j);
if(color == -1) {
builder.append(" ");
}
else {
builder.append("0 ");
}
}
builder.append("\r\n");
}
writer.write(builder.toString());
writer.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提取二值化后图片的边界
* 对二维码有奇效
* @param bufferedImage 原图片
* @return 二值化后的图片
*/
private static BufferedImage getImageBorder(BufferedImage bufferedImage) {
BufferedImage borderImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType());
//List toDealPoints = new ArrayList<>();
int imgWidth = bufferedImage.getWidth();
int imgHeight = bufferedImage.getHeight();
for (int i = 1; i < imgWidth - 1; i++) {
for (int j = 1; j < imgHeight - 1; j++) {
// 当前点
int color = bufferedImage.getRGB(i, j);
// 上点
int upColor = bufferedImage.getRGB(i, j - 1);
// 下点
int downColor = bufferedImage.getRGB(i, j + 1);
// 左点
int leftColor = bufferedImage.getRGB(i - 1, j);
// 右点
int rightColor = bufferedImage.getRGB(i + 1, j);
// 如果某个黑点的上下左右点都为黑点,就表示它不是边界,把它设为白点
if(isQualified(color, upColor, downColor, leftColor, rightColor)) {
// 白色
borderImage.setRGB(i, j, 0xFFFFFF);
}
else {
// 原色不变
borderImage.setRGB(i, j, color);
}
}
}
return borderImage;
}
/**
* 根据设置的阈值,判断当前点是否是边界点
* 判断规则如下:
* 如果当前点是白色的点,直接跳过
* 如果当前点不是白色,且它的上下左右4个点和它的差别都在阈值内,
* 那么就认为它不是边界点,返回true,否则返回false;
* @param color 当前点
* @param upColor 上点
* @param downColor 下点
* @param leftColor 左点
* @param rightColor 右点
* @return 是否设置为白色
*/
public static boolean isQualified(int color, int upColor, int downColor, int leftColor, int rightColor) {
// color == -1 表示白色,白色的不需要再设置为白色
return color != -1 && (Math.abs(color - upColor) < threshold
&& Math.abs(color - downColor) < threshold
&& Math.abs(color - leftColor) < threshold
&& Math.abs(color - rightColor) < threshold);
}
}