java绘制标注框,注册字体

news2024/12/23 5:10:39

文章目录

    • 场景
    • 思路
    • 步骤
      • 1.注册字体
      • 2.绘制标注框保存文本
      • 3.效果如下:

场景

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

开通了个微信公众号:
搜索: 怒放de每一天
后续可能不定时推送相关文章,期待和大家一起成长!!

在这里插入图片描述


大功告成!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第三章:JVM监控及诊断工具-GUI篇

JVM监控及诊断工具-GUI篇 使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息&#xff0c;但它们存在下列局限: 无法获取方法级别的分析数据&#xff0c;如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。要求用户登录到…

手写一个react,看透react运行机制

适合人群 本文适合0.5~3年的react开发人员的进阶。 讲讲废话&#xff1a; react的源码&#xff0c;的确是比vue的难度要深一些&#xff0c;本文也是针对初中级&#xff0c;本意让博友们了解整个react的执行过程。 写源码之前的必备知识点 JSX 首先我们需要了解什么是JSX。…

Flutter高仿微信-第19篇-支付-我的零钱

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 实现代码&#xff1a; /*** Author : wangning* Email : maoning20080809163.…

Unity DOTS学习 前置知识(一)

DOTS是什么 Data-Oriented Technology Stack(面向数据的技术栈) Unity 使用的5个核心包&#xff1a; The C# job system 提供快速安全的多线程操作The Burst compiler 优化C#代码的编译器&#xff0c;能够编译生成比mono或者L2CPP更快的代码。可以编译Unity中的任何代码Unit…

解决传统难题,WMS系统实现信息数据实时追踪

随着社会经济的发展&#xff0c;传统仓库的存储和做工难以适应当下市场经济的需求。仓库需要进行转型升级&#xff0c;从而适应当下的环境。在仓库的转型升级过程当中&#xff0c;WMS系统是不可或缺的一部分内容。 而WMS系统的应用会从多方面支持仓库的转型升级&#xff0c;其带…

uniapp之最新获取用户昵称以及头像

前言 在uniapp登录时候最开始想的就是手机号登录之后&#xff0c;就获取用户的昵称以及头像&#xff0c;存储起来&#xff0c;登录的时候直接显示在我的页面&#xff0c;最开始使用的是 uniapp官网自带的uni.getUserProfile的方法&#xff0c;就可以获取用户的头像跟昵称&…

Prometheus Operator与kube-prometheus之二-如何监控1.23+ kubeadm集群

简介 系列文章: 标签 - Prometheus - 东风微鸣技术博客 (ewhisper.cn)Prometheus Operator 的上一篇: Prometheus Operator 与 kube-prometheus 之一 - 简介 - 东风微鸣技术博客 (ewhisper.cn) kube-prometheus-stack捆绑了监控Kubernetes 集群所需的Prometheus Operator、Ex…

Web(二)html5基础-表格基本结构

第1关_网页表格的基本概念 第2关_创建简单的表格 本关任务&#xff1a;创建一个两行两列的表格。 相关知识&#xff1a;为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.表格的结构及对应的标签&#xff0c;2.表格标签的属性。 表格的结构及对应的标签。一个基本的表格是…

Netty系列(二):Netty拆包/沾包问题的解决方案

上一篇说到Netty系列&#xff08;一&#xff09;&#xff1a;Springboot整合Netty&#xff0c;自定义协议实现&#xff0c;本文聊一些拆包/沾包问题。 拆包/沾包问题 TCP是面向字节流的协议&#xff0c;在发送方发送的若干包数据到接收方接收时&#xff0c;这些数据包可能会被…

Linux笔记

一。基础思想 一切皆文件。 两条权限原则&#xff1a; 权限分组原则权限最小原则 su是切换用户&#xff0c;而sudo则是用root权限执行某操作&#xff08; 普通用户sudo安全&#xff09; Linux目录 系统只存在一颗文件树、从/开始&#xff0c;所有的文件都挂载在这个节点上。…

JaCoCo增量覆盖率的基本实现原理

什么是增量覆盖率 如图所示&#xff0c;在master分支提交了HelloController&#xff0c;然后从master拉了个新分支test&#xff1b;提交了第1次代码&#xff0c;增加了WorldController&#xff1b;提交了第2次代码&#xff0c;增加了DonController。增量的获取方式有两种&#…

报表工具使用教程-FineReport决策报表导出Plus

前言 通过决策报表导出插件&#xff0c;用户可以将单张决策报表导出为 Excel &#xff0c;PDF&#xff0c;Word 格式文件。 那么用户如何将决策报表导出为 PPT 或 Image 格式文件呢&#xff1f;如何将多张决策报表合并导出至一个文件呢&#xff1f; 1.实现思路 用户通过安装…

静态时序分析简明教程(七)]端口延迟

端口延迟一、写在前面1.1 快速导航链接二、端口延迟2.1 输入有效2.2 输出有效2.3 set_input_delay2.3.1 -clock clock_name2.3.2 -clock_fall2.3.3 -level_sensitive2.3.4 -rise/fall2.3.5 min/max2.3.6 -add_delay2.3.7 时钟延迟2.4 set_output_delay三、总结一、写在前面 一…

点击化学FAM荧光素:6-FAM-alkyne,FAM alkyne 6-isomer,6-炔基-羧基荧光素

【中文名称】6-炔基-羧基荧光素 【英文名称】 FAM alkyne,6-isomer&#xff0c;6-FAM-alkyne 【CAS】478801-49-9 【分子式】C24H15NO6 【分子量】413.39 【纯度标准】95% 【包装规格】25mg&#xff0c;50mg&#xff0c;100mg 【是否接受定制】可进行定制&#xff0c;定制时间周…

Kubernetes安装可视化界面

安装可视化界面编写配置文件安装kubernetes-dashboard创建访问账号访问可视化界面dashboard是kubernetes官方提供的可视化界面。 https://github.com/kubernetes/dashboard编写配置文件 创建配置文件存放目录并切换到其中&#xff1a; mkdir /usr/local/kubernetes-dashboard…

java面试强基(10)

Exception 和 Error 有什么区别&#xff1f; 在 Java 中&#xff0c;所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类: Exception :程序本身可以处理的异常&#xff0c;可以通过 catch 来进行捕获。Exception 又可以分为 Checke…

Twitter网红账号营销,一定不能做的事

做社媒运营&#xff0c;我们都会创建一个官方账号与粉丝进行互动沟通&#xff0c;及时通知我们的新活动、产品&#xff0c;也是我们与粉丝建立联系的一个渠道方法。 推特群推王提示&#xff0c;虽然有这么多的好处&#xff0c;但是&#xff0c;也是有很多事项需要注意的&#…

服务器抓包简介

1、微服务服务器上抓包 2、在nginx服务器上抓包 1、服务器安装抓包软件 yum install -y tcpdump 2、服务器抓包命令 tcpdump -i any -s 0 -vvv -w /opt/qqgh.cap port 8080&#xff08;本服务器该服务的实际ip地址&#xff09; tcpdump -i eth0 host 10.30.224.170 -w result.…

14.函数的使用

函数的概念 函数是c语言的功能单位&#xff0c;实现一个功能可以封装成一个函数来实现。 定义函数的时候一切以功能为目的&#xff0c;根据功能去定函数的参数和返回值。 函数的分类 1.从定义角度分类&#xff08;即函数是谁实现的&#xff09; 库函数&#xff08;c库实现的…

Fedora怎么设置主菜单快捷键? Fedora快捷键的设置方法

Fedora主菜单可以设置打开快捷键&#xff0c;该怎么设置呢&#xff1f;下面我们就来看看Fedora快捷键的操作方法。 同时按【ALTF2】&#xff0c;输入gnome-terminal&#xff0c;打开终端。 单击右上角的主菜单按钮。 单击【配置文件首选项】。 单击【快捷键】。 单击【显示主菜…