场景
有个项目需要在java的后台将AI算法的标识框,置信度值,画到上传的报警图片上。以前都在算法部分画,但是效率有点低,所以传过来原始的图片(也会用来训练用)和标识的位置信息移到前端或者java应用端画,但是我这边又有推送给第三方平台的业务,不得不在java端画了。 (“前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。)因此,今天介绍下我这边是如何实现的:
思路
测试: 可以使用openCV,但是这个玩意因为引入之后发现jar包太大了,不得不放弃了,使用java自带的画笔Graphics2D
去实现,画出来后保存到本地查看效果。
生产: 生产上,不需要保存本地了,直接将原始图片画完标识框等信息,返回base64推送给第三方接口就可了。
步骤
1.注册字体
这个纯属经验,以前遇到windows上可以使用微软雅黑,linux不可以的情况,一种方式是将windows的字体放入linux系统上,但是对于已经部署的项目来说不是很友好,另一种方式是将微软雅黑字体放入java项目的resource文件下,然后注册进去,我是用的这种方式:
可以在这个网站现在微软雅黑字体,重命名即可。
代码如下:
import java.awt.*; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class InitFontUtil { /** * 注册字体 * * @return */ public static Map<String, Object> getGraphicsFontAndEnv() { GraphicsEnvironment localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); Map<String, Object> map = new HashMap<>(); Font font = null; Font font30 = null; // if(isWindows()){ // String typeface = "Microsoft YaHe"; // return new Font(typeface, Font.BOLD, 24); // } try { // if (localGraphicsEnvironment.getAvailableFontFamilyNames() == null || localGraphicsEnvironment.getAvailableFontFamilyNames().length == 0) { font = Font.createFont(Font.TRUETYPE_FONT, InitFontUtil.class.getResourceAsStream("/msyhbd.ttf")); font30 = Font.createFont(Font.TRUETYPE_FONT, InitFontUtil.class.getResourceAsStream("/msyhbd.ttf")); font = font.deriveFont(Font.BOLD, 24f); font30 = font30.deriveFont(Font.BOLD, 30f); localGraphicsEnvironment.registerFont(font); localGraphicsEnvironment.registerFont(font30); map.put("font24", font); map.put("font30", font30); map.put("env", localGraphicsEnvironment); // } else { // if (!isWindows()) { // Font[] allFonts = localGraphicsEnvironment.getAllFonts(); // if (allFonts != null && allFonts.length > 0) { // Font nowFont = allFonts[0]; // return nowFont; // } // } // } } catch (FontFormatException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return map; } /** * 是否是windows系统 * * @return */ public static boolean isWindows() { return System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0 ? true : false; } }
2.绘制标注框保存文本
PositionDto 标注框位置信息实体
package com.edgemgmt.system.domain; import java.math.BigDecimal; public class PositionDto { private BigDecimal x; private BigDecimal w; private BigDecimal h; private String txt; private BigDecimal y; private BigDecimal conf; public BigDecimal getX() { return x; } public void setX(BigDecimal x) { this.x = x; } public BigDecimal getW() { return w; } public void setW(BigDecimal w) { this.w = w; } public BigDecimal getH() { return h; } public void setH(BigDecimal h) { this.h = h; } public String getTxt() { return txt; } public void setTxt(String txt) { this.txt = txt; } public BigDecimal getY() { return y; } public void setY(BigDecimal y) { this.y = y; } public BigDecimal getConf() { return conf; } public void setConf(BigDecimal conf) { this.conf = conf; } @Override public String toString() { return "PositionDto{" + "x=" + x + ", w=" + w + ", h=" + h + ", txt='" + txt + '\'' + ", y=" + y + ", conf=" + conf + '}'; } }
ImageBase64Utils 画框的工具类
package com.edgemgmt.system.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.edgemgmt.system.domain.PositionDto; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.font.FontDesignMetrics; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.List; import java.util.Map; /* //img = cv2.rectangle( // img, // (int(x * width), int(y * height)), // (int(x * width + w * width), int(y * height + h * height)), // (0, 0, 255), // 5 // ) */ public class ImageBase64Utils { private static final Logger log = LoggerFactory.getLogger(ImageBase64Utils.class); private static String frefix = "png"; public static String typeface = "Microsoft YaHei"; //[{"txt":"异常烟火","w":0.18281249701976776,"h":0.3125,"x":0.08124999701976776,"y":0.42934781312942505,"conf":0.8802329301834106}] public static void main(String[] args) throws IOException { File file = new File("D:\\002.jpg"); String s = "[{\"txt\":\"人员打电话\",\"w\":0.03072916716337204,\"h\":0.08796296268701553,\"x\":0.5708333253860474,\"y\":0.39629629254341125,\"conf\":0.7689861059188843},{\"txt\":\"人员打电话\",\"w\":0.05104166641831398,\"h\":0.10555555671453476,\"x\":0.5510416626930237,\"y\":0.3444444537162781,\"conf\":0.8071668148040771}]"; //String s1 = "[{\"txt\":\"未戴口罩\",\"w\":0.07187499850988388,\"h\":0.17499999701976776,\"x\":0.6848958134651184,\"y\":0.19722221791744232,\"conf\":0.8080925941467285}]"; String s1 = getBase64FromFile(file, s); log.info(s1); } public static String getBase64FromFile(File file, String positionJsonStr) { try { BufferedImage bimg = ImageIO.read(new FileInputStream(file)); //大小 long size = file.length() / 1024; // 宽度 int width = bimg.getWidth(); // 高度 int height = bimg.getHeight(); Graphics2D g2d = getG2d(bimg); log.info("图片大小:{} kb;图片宽度:{} 像素;图片高度:{} 像素", size, width, height); return convertBase64FromImage(bimg, width, height, g2d, positionJsonStr); } catch (Exception e) { return null; } } //BufferedImage 转base64 public static String getBase64FromImage(BufferedImage img) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { // 设置图片的格式 ImageIO.write(img, "jpg", stream); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } byte[] bytes = Base64.encodeBase64(stream.toByteArray()); String base64 = new String(bytes); return "data:image/jpeg;base64," + base64; } private static String convertBase64FromImage(BufferedImage bimg, int width, int height, Graphics2D g2d, String s) { Type type = new TypeReference<List<PositionDto>>() { }.getType(); List<PositionDto> list = JSON.parseObject(s, type); for (int i = 0; i < list.size(); i++) { PositionDto item = list.get(i); //获取 标注框的 起点(x,y) BigDecimal boxX = item.getX().multiply(new BigDecimal(width)); BigDecimal boxY = item.getY().multiply(new BigDecimal(height)); //获取 标注框的 宽度和高度 BigDecimal boxWidth = (item.getW()).multiply(new BigDecimal(width)); BigDecimal boxHeight = (item.getH()).multiply(new BigDecimal(height)); // 标注框设置宽度 g2d.setStroke(new BasicStroke(5)); g2d.setColor(Color.RED); //绘制标注框 g2d.drawRect(boxX.intValue(), boxY.intValue(), boxWidth.intValue(), boxHeight.intValue() - 5); //绘制文字背景 // //获取文字 起点 (x,y) int fontBgX = boxX.intValue(); int fontBgY = boxY.intValue() - 3; // Font font = new Font(typeface, Font.BOLD, 30); Map<String, Object> graphicsFontAndEnv = InitFontUtil.getGraphicsFontAndEnv(); Font font = (Font) graphicsFontAndEnv.get("font30"); String content = item.getConf().setScale(2, BigDecimal.ROUND_DOWN).toString(); FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font); int contentWidth = getWordWidth(font, content);//计算文字的宽 int contentHeight = metrics.getHeight();//计算高 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); //设置背影为黑色 g2d.setColor(Color.BLACK); g2d.fillRect(fontBgX, fontBgY - 30, contentWidth, 30); //绘制文字的底框 g2d.setFont(font); g2d.setColor(Color.WHITE); g2d.drawString(content, fontBgX, fontBgY - 3);//图片上写文字 } // 释放对象 g2d.dispose(); String base64FromImage = getBase64FromImage(bimg); try { InputStream inputStream = upload(bimg); writeToLocal("D://test" + 4 + ".png", inputStream); } catch (Exception e) { e.printStackTrace(); } return base64FromImage; } //字体的一段字符串的宽 public static int getWordWidth(Font font, String content) { FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font); int width = 0; for (int i = 0; i < content.length(); i++) { width += metrics.charWidth(content.charAt(i)); } return width; } private static void writeToLocal(String destination, InputStream input) throws IOException { int index; byte[] bytes = new byte[1024]; FileOutputStream downloadFile = new FileOutputStream(destination); while ((index = input.read(bytes)) != -1) { downloadFile.write(bytes, 0, index); downloadFile.flush(); } downloadFile.close(); input.close(); } public static InputStream upload(BufferedImage bimg) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); //保存新图片 ImageIO.write(bimg, frefix, os); InputStream is = new ByteArrayInputStream(os.toByteArray()); return is; } private static Graphics2D getG2d(BufferedImage bimg) { //得到Graphics2D 对象 Graphics2D g2d = (Graphics2D) bimg.getGraphics(); // 抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); return g2d; } }
这个类有3个作用
- 绘制标注框,字体背景图和字体。
- 写入绘制后的图片到本地
- 将绘制后的图片转成base64
3.效果如下:
控制台也打印了base64字符,以后前端可以直接用base64显示图片:
自此,java就实现了标注框的功能。
在需要调用的地方直接这样调用即可
//画框的base64赋值 //原始图片 sysEventRecord.getPicUrl() //标注框的位置信息 sysEventRecord.getPosition() sysEventRecordVo.setPicUrlBase64(ImageBase64Utils.getBase64FromFile(new File(sysEventRecord.getPicUrl()), sysEventRecord.getPosition()));
大功告成!!