java绘制标注框,注册字体

简介: java绘制标注框,注册字体


场景

有个项目需要在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()));




大功告成!!

相关文章
|
10月前
|
前端开发 Java 数据库连接
Java代码打造的注册系统
Java代码打造的注册系统
87 0
|
10月前
|
前端开发 Java 数据库连接
JavaWeb:登录注册功能实现
JavaWeb 登录注册是一种常见的网站开发功能,使用 Java 编程语言和 Web 技术来实现用户注册和登录功能
285 3
|
缓存 Dubbo Java
Java面试题顶级理解!Dubbo注册中心挂了,服务还可以继续通信吗?
什么情况?一位工作了 5 年的 Java 程序员,竟然回答不出这个问题?说“Dubbo注册中心挂了, 服务之间还可以继续通信吗”?今天,我话2分钟时间给大家来聊一聊。
300 0
|
10月前
|
前端开发 Java 数据库连接
【Java实战篇】SpringBoot+MyBatis快速实现登录注册
【1月更文挑战第19天】【Java实战篇】SpringBoot+MyBatis快速实现登录注册
|
7月前
|
Java
Java Bean 注册对象
Java Bean 注册对象
39 0
|
8月前
|
缓存 开发者 Java
java枚举消除冗余代码问题之需要延迟注册枚举到缓存问题如何解决
java枚举消除冗余代码问题之需要延迟注册枚举到缓存问题如何解决
|
9月前
|
安全 Java 开发者
Java一分钟之-Spring Cloud Netflix Eureka:服务注册与发现
【6月更文挑战第8天】Spring Cloud Eureka是微服务架构的关键,提供服务注册与发现功能。本文讲解Eureka工作原理、配置、常见问题及解决方案。Eureka包含Server(管理服务状态)和Client(注册服务实例并发现服务)。快速入门包括启动Eureka Server和创建Eureka Client。常见问题涉及服务注册不上、服务下线和客户端注册信息不准确,可通过检查网络、理解自我保护机制和配置元数据解决。此外,文中还提及健康检查、安全配置和集群部署等高级实践,以增强系统健壮性和扩展性。
322 8
|
8月前
|
负载均衡 Java Maven
Java中的微服务治理与服务注册
Java中的微服务治理与服务注册
|
8月前
|
存储 负载均衡 Java
Java中的服务注册与发现原理与实现
Java中的服务注册与发现原理与实现
|
9月前
|
Java 开发者
java开发者工具IDEA自定义设置主题/字体/字号大小
java开发者工具IDEA自定义设置主题/字体/字号大小
776 0