学习yolo+Java+opencv简单案例(一)

news2024/10/1 23:49:26

目录

一、大概架构

二、编写pom.xml

1、yolo-study模块(root):

2、CameraDetection模块

三、编写yml配置文件

四、编写controller

五,可能会出现的问题

1、修改VM启动参数:

2、修改启动类

六、测试

七,工具模块代码

ODConfig:

Detection:

ODResult:

ImageUtil:

Letterbox:

八、添加告警判断示例

前言:关于这篇文章我也是学习了Gitee上一位大佬的开源代码,然后对代码加上自己的一些理解和修修补补,当作自己的一个学习笔记吧,最后:欢迎支持原版~~

那位大佬源代码的Gitee地址为:yolo-onnx-java: 基于Java开发的全能视觉智能识别项目 java 调用 yolo onnx 模型 AI 视频 识别 支持 yolov5 yolov8 yolov7 yolov9 yolov10,包含 预处理 和 后处理 。java 目标检测 目标识别,可集成 rtsp rtmp (gitee.com)

对于这次案例我也是通过接口的方式来实现的,不是demo的形式,我觉得这样子才符合实际应用

在文章的最后附上我的代码地址

一、大概架构

这个是代码的大概架构,其中红色方框需要去大佬的git仓库自行下载。

解释一下里面两个文件:

1、opencv_videoio_ffmpeg.dll:

        类型:动态链接库(Dynamic Link Library,DLL)。

        用途:这是OpenCV库中的一个特定组件,专门用于支持视频输入输出的功能。它基于FFmpeg库,实现了对多种视频格式(如AVI、MP4、MOV等)的读取和写入操作。在使用OpenCV进行视频处理时,如果系统自带的解码器无法处理某些视频格式,OpenCV会尝试加载此DLL以提供额外的编解码支持。

2、yolo7-tiny.onnx:

        这个是yolo7的模型权重文件,这个也可以自行去yolo7的git仓库自行下载

下面这个是工具模块,里面的文件也可以去大佬的仓库自行下载,在文章的最后我也会给出代码

二、编写pom.xml

1、yolo-study模块(root):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bluefoxyu</groupId>
    <artifactId>yolo-study</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>predict-test</module>
        <module>CameraDetection</module>
        <module>yolo-common</module>
    </modules>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.microsoft.onnxruntime</groupId>
            <artifactId>onnxruntime</artifactId>
            <version>1.16.1</version>
        </dependency>
        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>4.7.0-0</version>
        </dependency>
    </dependencies>

</project>
2、CameraDetection模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.bluefoxyu</groupId>
        <artifactId>yolo-study</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>CameraDetection</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.bluefoxyu</groupId>
            <artifactId>yolo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.2.4</version>
        </dependency>
    </dependencies>

</project>

三、编写yml配置文件

server:
  port: 8080

spring:
  profiles:
    active: dev

四、编写controller

/**
 * 摄像头识别,这是yolov7的视频识别例子,v5和v8的根据下面的思路,将其他文件中的代码复制过来即可
 * 视频帧率15最佳,20也可以,不建议30,分辨率640最佳,720也可以。不建议1080,码率不要超过2048,1024最佳。可在摄像头自带的管理页面中设备,主码流和子码流
 */
@RestController
@RequestMapping("/camera")
public class CameraDetectionController {

    // 用于停止检测
    //volatile关键字保证了多线程环境下的可见性,确保所有线程看到这个变量的值是一致的,即当一个线程修改了 volatile 变量的值,这个新值对于其他线程来说是立即可见的
    private static volatile boolean stopRequested = false;
    private static volatile boolean startRequested = false;

    //摄像头检测
    @PostMapping("/detect")
    public String CameraDetection() throws OrtException {
        //System.load(ClassLoader.getSystemResource("lib/opencv_videoio_ffmpeg470_64.dll").getPath());
        nu.pattern.OpenCV.loadLocally();

        //linux和苹果系统需要注释这一行,如果仅打开摄像头预览,这一行没有用,可以删除,如果rtmp或者rtsp等等这一样有用,也可以用pom依赖代替
        String OS = System.getProperty("os.name").toLowerCase();
        if (OS.contains("win")) {
            System.load(ClassLoader.getSystemResource("lib/opencv_videoio_ffmpeg470_64.dll").getPath());
        }
        //yolov7的ONNX模型文件路径
        String model_path = "./CameraDetection/src/main/resources/model/yolov7-tiny.onnx";

        // 用于识别的标签,labels 数组包含了模型可以识别的目标类别
        String[] labels = {
                "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train",
                "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter",
                "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear",
                "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase",
                "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
                "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle",
                "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
                "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut",
                "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet",
                "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave",
                "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors",
                "teddy bear", "hair drier", "toothbrush"};

        // 加载ONNX模型
        OrtEnvironment environment = OrtEnvironment.getEnvironment();
        OrtSession.SessionOptions sessionOptions = new OrtSession.SessionOptions();

        // 使用gpu,需要本机按钻过cuda,并修改pom.xml,不安装也能运行本程序
        // sessionOptions.addCUDA(0);
        // 实际项目中,视频识别必须开启GPU,并且要防止队列堆积

        OrtSession session = environment.createSession(model_path, sessionOptions);
        // 输出基本信息
        session.getInputInfo().keySet().forEach(x -> {
            try {
                System.out.println("input name = " + x);
                System.out.println(session.getInputInfo().get(x).getInfo().toString());
            } catch (OrtException e) {
                throw new RuntimeException(e);
            }
        });

        // 加载标签及颜色
        ODConfig odConfig = new ODConfig();
        VideoCapture video = new VideoCapture();

        // 也可以设置为rtmp或者rtsp视频流:video.open("rtmp://192.168.1.100/live/test"), 海康,大华,乐橙,宇视,录像机等等
        // video.open("rtsp://192.168.1.100/live/test")
        // 也可以静态视频文件:video.open("video/car3.mp4");  flv 等
        // 不持支h265视频编码,如果无法播放或者程序卡住,请修改视频编码格式
        video.open(0);  //获取电脑上第0个摄像头
        //video.open("images/car2.mp4"); //不开启gpu比较卡

        //可以把识别后的视频在通过rtmp转发到其他流媒体服务器,就可以远程预览视频后视频,需要使用ffmpeg将连续图片合成flv 等等,很简单。
        if (!video.isOpened()) {
            System.err.println("打开视频流失败,未检测到监控,请先用vlc软件测试链接是否可以播放!,下面试用默认测试视频进行预览效果!");
            video.open("video/car3.mp4");
        }

        // 在这里先定义下框的粗细、字的大小、字的类型、字的颜色(按比例设置大小粗细比较好一些)
        int minDwDh = Math.min((int)video.get(Videoio.CAP_PROP_FRAME_WIDTH), (int)video.get(Videoio.CAP_PROP_FRAME_HEIGHT));
        int thickness = minDwDh / ODConfig.lineThicknessRatio;
        double fontSize = minDwDh / ODConfig.fontSizeRatio;
        int fontFace = Imgproc.FONT_HERSHEY_SIMPLEX;

        Mat img = new Mat();

        // 跳帧检测,一般设置为3,毫秒内视频画面变化是不大的,快了无意义,反而浪费性能
        int detect_skip = 4;

        // 跳帧计数
        int detect_skip_index = 1;

        // 最新一帧也就是上一帧推理结果
        float[][] outputData   = null;

        //当前最新一帧。上一帧也可以暂存一下
        Mat image;

        Letterbox letterbox = new Letterbox();
        OnnxTensor tensor;

        // 使用多线程和GPU可以提升帧率,线上项目必须多线程!!!,一个线程拉流,将图像存到[定长]队列或数组或者集合,一个线程模型推理,中间通过变量或者队列交换数据,代码示例仅仅使用单线程
        while (video.read(img)) {
            if ((detect_skip_index % detect_skip == 0) || outputData == null){
                // 缓存上一帧图像
                image = img.clone();
                image = letterbox.letterbox(image);
                Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2RGB);

                image.convertTo(image, CvType.CV_32FC1, 1. / 255);
                float[] whc = new float[3 * 640 * 640];
                image.get(0, 0, whc);
                float[] chw = ImageUtil.whc2cwh(whc);

                detect_skip_index = 1;

                FloatBuffer inputBuffer = FloatBuffer.wrap(chw);
                tensor = OnnxTensor.createTensor(environment, inputBuffer, new long[]{1, 3, 640, 640});

                HashMap<String, OnnxTensor> stringOnnxTensorHashMap = new HashMap<>();
                stringOnnxTensorHashMap.put(session.getInputInfo().keySet().iterator().next(), tensor);

                // 运行推理
                // 模型推理本质是多维矩阵运算,而GPU是专门用于矩阵运算,占用率低,如果使用cpu也可以运行,可能占用率100%属于正常现象,不必纠结。
                OrtSession.Result output = session.run(stringOnnxTensorHashMap);

                // 得到结果,缓存结果
                try{
                    outputData = (float[][]) output.get(0).getValue();
                }catch (OrtException e){}
            }else{
                detect_skip_index = detect_skip_index + 1;
            }
            // 画框和文字
            for(float[] x : outputData){

                ODResult odResult = new ODResult(x);
                // 业务逻辑写在这里,注释下面代码,增加自己的代码,根据返回识别到的目标类型,编写告警逻辑。等等
                // 实际项目中建议不要在视频画面上画框和文字,只告警,或者在告警图片上画框。画框和文字对视频帧率影响非常大
                // 画框
                Point topLeft = new Point((odResult.getX0() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY0() - letterbox.getDh()) / letterbox.getRatio());
                Point bottomRight = new Point((odResult.getX1() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY1() - letterbox.getDh()) / letterbox.getRatio());
                Scalar color = new Scalar(odConfig.getOtherColor(odResult.getClsId()));

                Imgproc.rectangle(img, topLeft, bottomRight, color, thickness);
                // 框上写文字
                String boxName = labels[odResult.getClsId()];
                Point boxNameLoc = new Point((odResult.getX0() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY0() - letterbox.getDh()) / letterbox.getRatio() - 3);

                // 也可以二次往视频画面上叠加其他文字或者数据,比如物联网设备数据等等
                Imgproc.putText(img, boxName, boxNameLoc, fontFace, 0.7, color, thickness);
                // System.out.println(odResult+"   "+ boxName);

            }

            // 保存告警图像到同级目录
            // Imgcodecs.imwrite(ODConfig.savePicPath, img);
            //服务器部署:由于服务器没有桌面,所以无法弹出画面预览,主要注释一下代码
            HighGui.imshow("result", img);

            startRequested=true;

            if (stopRequested) {
                break;
            }

            // 多次按任意按键关闭弹窗画面,结束程序
            // 需要使用键盘的键入,不能使用鼠标手动关闭
            if(HighGui.waitKey(1) != -1){
                break;
            }

        }

        HighGui.destroyAllWindows();
        video.release();
        stopRequested = false;  // 重置状态以便下一次调用
        startRequested=false;   // 重置状态以便下一次调用
        //System.exit(0);

        System.out.println("该次检测成功~~");

        return "摄像头检测成功";
    }

    @PostMapping("/stop")
    public String stopDetection() {
        if (!startRequested){
            return "没有正在进行的检测";
        }
        stopRequested = true;
        System.out.println("执行停止检测");
        return "请求停止检测成功";
    }

}

在代码中我写了两个接口,一个用于开启检测,一个用于停止检测。在代码的逻辑里面我加入stopRequested,startRequested这两个参数用于判断是否开启检测和作为停止检测的手段,也保留了原作者的多次点击键盘就停止检测的方法。

五,可能会出现的问题

可能会报这样子的错误:java.awt.HeadlessException: null

解决办法有两种:

1、修改VM启动参数:
java.awt.HeadlessException: null

2、修改启动类
@SpringBootApplication
public class CameraDetectionApplication {
    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(CameraDetectionApplication.class);
        builder.headless(false).run(args);
        /*SpringApplication.run(CameraDetectionApplication.class, args);*/
    }
}

参考:

项目总结:部署springboot项目报java.awt.HeadlessException: null_springapplicationbuilder.headless-CSDN博客
 

六、测试

测试开启检测接口:

http://localhost:8080/camera/detect

测试停止检测接口:

http://localhost:8080/camera/stop

效果如下:

七,工具模块代码

先看看大概架构:

ODConfig:
package com.bluefoxyu.config;

import java.util.*;

public final class ODConfig {


    public static final Integer lineThicknessRatio = 333;
    public static final Double fontSizeRatio = 1080.0;

    private static final List<String> default_names = new ArrayList<>(Arrays.asList(
            "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train",
            "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter",
            "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear",
            "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase",
            "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
            "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle",
            "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
            "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut",
            "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet",
            "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave",
            "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors",
            "teddy bear", "hair drier", "toothbrush"));


    private static final List<String> names = new ArrayList<>(Arrays.asList(
            "no_helmet", "helmet"));

    private final Map<String, double[]> colors;

    public ODConfig() {
        this.colors = new HashMap<>();
        default_names.forEach(name->{
            Random random = new Random();
            double[] color = {random.nextDouble()*256, random.nextDouble()*256, random.nextDouble()*256};
            colors.put(name, color);
        });
    }

    public String getName(int clsId) {
        return names.get(clsId);
    }

    public double[] getColor(int clsId) {
        return colors.get(getName(clsId));
    }

    public double[] getNameColor(String Name){
        return colors.get(Name);
    }

    public double[] getOtherColor(int clsId) {
        return colors.get(default_names.get(clsId));
    }
}
Detection:
package com.bluefoxyu.domain;

public class Detection {

    public String label;

    private Integer clsId;

    public float[] bbox;

    public float confidence;


    public Detection(String label, Integer clsId, float[] bbox, float confidence){
        this.clsId = clsId;
        this.label = label;
        this.bbox = bbox;
        this.confidence = confidence;
    }

    public Detection(){

    }

    public Integer getClsId() {
        return clsId;
    }

    public void setClsId(Integer clsId) {
        this.clsId = clsId;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public float[] getBbox() {
        return bbox;
    }

    public void setBbox(float[] bbox) {
    }


    @Override
    public String toString() {
        return "  label="+label +
                " \t clsId="+clsId +
                " \t x0="+bbox[0] +
                " \t y0="+bbox[1] +
                " \t x1="+bbox[2] +
                " \t y1="+bbox[3] +
                " \t score="+confidence;
    }
}
ODResult:
package com.bluefoxyu.domain;

import java.text.DecimalFormat;


public class ODResult {
    private final Integer batchId;
    private final Float x0;
    private final Float y0;
    private final Float x1;
    private final Float y1;
    private final Integer clsId;
    private final Float score;

    public ODResult(float[] x) {
        this.batchId = (int) x[0];
        this.x0 = x[1];
        this.y0 = x[2];
        this.x1 = x[3];
        this.y1 = x[4];
        this.clsId = (int) x[5];
        this.score = x[6];
    }

    public Integer getBatchId() {
        return batchId;
    }

    public Float getX0() {
        return x0;
    }

    public Float getY0() {
        return y0;
    }

    public Float getX1() {
        return x1;
    }

    public Float getY1() {
        return y1;
    }

    public Integer getClsId() {
        return clsId;
    }

    public Float setX0(Float x0) {
        return this.x0;
    }

    public Float setY0(Float y0) {
        return this.y0;
    }

    public Float setX1(Float x1) {
        return this.x1;
    }

    public Float setY1(Float y1) {
        return this.y1;
    }



    public String getScore() {
        DecimalFormat df = new DecimalFormat("0.00");
        return df.format(this.score);
    }

    @Override
    public String toString() {
        return " batchId=" + batchId +
                " \t x0=" + x0 +
                " \t y0=" + y0 +
                " \t x1=" + x1 +
                " \t y1=" + y1 +
                " \t clsId=" + clsId +
                " \t score=" + getScore();
    }
}
ImageUtil:
package com.bluefoxyu.utils;

import com.bluefoxyu.domain.Detection;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;

import java.util.List;


public class ImageUtil {

    public static Mat resizeWithPadding(Mat src, int width, int height) {

        Mat dst = new Mat();
        int oldW = src.width();
        int oldH = src.height();

        double r = Math.min((double) width / oldW, (double) height / oldH);

        int newUnpadW = (int) Math.round(oldW * r);
        int newUnpadH = (int) Math.round(oldH * r);

        int dw = (width - newUnpadW) / 2;
        int dh = (height - newUnpadH) / 2;

        int top = (int) Math.round(dh - 0.1);
        int bottom = (int) Math.round(dh + 0.1);
        int left = (int) Math.round(dw - 0.1);
        int right = (int) Math.round(dw + 0.1);

        Imgproc.resize(src, dst, new Size(newUnpadW, newUnpadH));
        Core.copyMakeBorder(dst, dst, top, bottom, left, right, Core.BORDER_CONSTANT);

        return dst;

    }

    public static void resizeWithPadding(Mat src, Mat dst, int width, int height) {

        int oldW = src.width();
        int oldH = src.height();

        double r = Math.min((double) width / oldW, (double) height / oldH);

        int newUnpadW = (int) Math.round(oldW * r);
        int newUnpadH = (int) Math.round(oldH * r);

        int dw = (width - newUnpadW) / 2;
        int dh = (height - newUnpadH) / 2;

        int top = (int) Math.round(dh - 0.1);
        int bottom = (int) Math.round(dh + 0.1);
        int left = (int) Math.round(dw - 0.1);
        int right = (int) Math.round(dw + 0.1);

        Imgproc.resize(src, dst, new Size(newUnpadW, newUnpadH));
        Core.copyMakeBorder(dst, dst, top, bottom, left, right, Core.BORDER_CONSTANT);

    }

    public static void whc2cwh(float[] src, float[] dst, int start) {
        int j = start;
        for (int ch = 0; ch < 3; ++ch) {
            for (int i = ch; i < src.length; i += 3) {
                dst[j] = src[i];
                j++;
            }
        }
    }


    public void xywh2xyxy(float[] bbox) {
        float x = bbox[0];
        float y = bbox[1];
        float w = bbox[2];
        float h = bbox[3];

        bbox[0] = x - w * 0.5f;
        bbox[1] = y - h * 0.5f;
        bbox[2] = x + w * 0.5f;
        bbox[3] = y + h * 0.5f;
    }

    public void scaleCoords(float[] bbox, float orgW, float orgH, float padW, float padH, float gain) {
        // xmin, ymin, xmax, ymax -> (xmin_org, ymin_org, xmax_org, ymax_org)
        bbox[0] = Math.max(0, Math.min(orgW - 1, (bbox[0] - padW) / gain));
        bbox[1] = Math.max(0, Math.min(orgH - 1, (bbox[1] - padH) / gain));
        bbox[2] = Math.max(0, Math.min(orgW - 1, (bbox[2] - padW) / gain));
        bbox[3] = Math.max(0, Math.min(orgH - 1, (bbox[3] - padH) / gain));
    }

    public static float[] whc2cwh(float[] src) {
        float[] chw = new float[src.length];
        int j = 0;
        for (int ch = 0; ch < 3; ++ch) {
            for (int i = ch; i < src.length; i += 3) {
                chw[j] = src[i];
                j++;
            }
        }
        return chw;
    }

    public static byte[] whc2cwh(byte[] src) {
        byte[] chw = new byte[src.length];
        int j = 0;
        for (int ch = 0; ch < 3; ++ch) {
            for (int i = ch; i < src.length; i += 3) {
                chw[j] = src[i];
                j++;
            }
        }
        return chw;
    }

    public static void drawPredictions(Mat img, List<Detection> detectionList) {
        // debugging image
        for (Detection detection : detectionList) {

            float[] bbox = detection.getBbox();
            Scalar color = new Scalar(249, 218, 60);
            Imgproc.rectangle(img,
                    new Point(bbox[0], bbox[1]),
                    new Point(bbox[2], bbox[3]),
                    color,
                    2
            );
            Imgproc.putText(
                    img,
                    detection.getLabel(),
                    new Point(bbox[0] - 1, bbox[1] - 5),
                    Imgproc.FONT_HERSHEY_SIMPLEX,
                    .5, color,
                    1);
        }
    }

}
Letterbox:
package com.bluefoxyu.utils;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

public class Letterbox {

    private Size newShape ;
    private final double[] color = new double[]{114,114,114};
    private final Boolean auto = false;
    private final Boolean scaleUp = true;
    private Integer stride = 32;

    private double ratio;
    private double dw;
    private double dh;

    public Letterbox(int w, int h) {
        this.newShape = new Size(w, h);
    }

    public Letterbox() {
        this.newShape = new Size(640, 640);
    }

    public double getRatio() {
        return ratio;
    }


    public double getDw() {
        return dw;
    }

    public Integer getWidth() {
        return (int) this.newShape.width;
    }

    public Integer getHeight() {
        return (int) this.newShape.height;
    }

    public double getDh() {
        return dh;
    }

    public void setNewShape(Size newShape) {
        this.newShape = newShape;
    }

    public void setStride(Integer stride) {
        this.stride = stride;
    }

    public Mat letterbox(Mat im) { // 调整图像大小和填充图像,使满足步长约束,并记录参数

        int[] shape = {im.rows(), im.cols()}; // 当前形状 [height, width]
        // Scale ratio (new / old)
        double r = Math.min(this.newShape.height / shape[0], this.newShape.width / shape[1]);
        if (!this.scaleUp) { // 仅缩小,不扩大(一且为了mAP)
            r = Math.min(r, 1.0);
        }
        // Compute padding
        Size newUnpad = new Size(Math.round(shape[1] * r), Math.round(shape[0] * r));
        double dw = this.newShape.width - newUnpad.width, dh = this.newShape.height - newUnpad.height; // wh 填充
        if (this.auto) { // 最小矩形
            dw = dw % this.stride;
            dh = dh % this.stride;
        }
        dw /= 2; // 填充的时候两边都填充一半,使图像居于中心
        dh /= 2;
        if (shape[1] != newUnpad.width || shape[0] != newUnpad.height) { // resize
            Imgproc.resize(im, im, newUnpad, 0, 0, Imgproc.INTER_LINEAR);
        }
        int top = (int) Math.round(dh - 0.1), bottom = (int) Math.round(dh + 0.1);
        int left = (int) Math.round(dw - 0.1), right = (int) Math.round(dw + 0.1);
        // 将图像填充为正方形
        Core.copyMakeBorder(im, im, top, bottom, left, right, Core.BORDER_CONSTANT, new org.opencv.core.Scalar(this.color));
        this.ratio = r;
        this.dh = dh;
        this.dw = dw;
        return im;
    }
}

八、添加告警判断示例

对于新的案例我重新创建了一个模块,将里面的video文件挪到common模块看起来优雅一点

工具类其他的和上面的一样都不变~~

controller:

/**
 * 摄像头识别,告警判断示例
 */

@RestController
@RequestMapping("/camera-detection-warn")
public class CameraDetectionWarnController {

    // 用于停止检测
    //volatile关键字保证了多线程环境下的可见性,确保所有线程看到这个变量的值是一致的,即当一个线程修改了 volatile 变量的值,这个新值对于其他线程来说是立即可见的
    private static volatile boolean stopRequested = false;
    private static volatile boolean startRequested = false;

    // 标准的对象去重或者判断是否为同一个对象避免重复告警应该使用目标跟踪,但是这里使用数量来简易判断,不是标准用法
    static Map<String,Integer> last = new ConcurrentHashMap<>();
    static Map<String,Integer> current = new ConcurrentHashMap<>();

    static Map<String,Integer> count = new ConcurrentHashMap<>();

    @PostMapping("/detect")
    public String CameraDetectionWarn()throws OrtException{
        //System.load(ClassLoader.getSystemResource("lib/opencv_videoio_ffmpeg470_64.dll").getPath());
        nu.pattern.OpenCV.loadLocally();

        //linux和苹果系统需要注释这一行,如果仅打开摄像头预览,这一行没有用,可以删除,如果rtmp或者rtsp等等这一样有用,也可以用pom依赖代替
        String OS = System.getProperty("os.name").toLowerCase();
        if (OS.contains("win")) {
            System.load(ClassLoader.getSystemResource("lib/opencv_videoio_ffmpeg470_64.dll").getPath());
        }
        String model_path = "./CameraDetectionWarn/src/main/resources/model/yolov7-tiny.onnx";

        String[] labels = {
                "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train",
                "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter",
                "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear",
                "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase",
                "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
                "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle",
                "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
                "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut",
                "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet",
                "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave",
                "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors",
                "teddy bear", "hair drier", "toothbrush"};

        // 加载ONNX模型
        OrtEnvironment environment = OrtEnvironment.getEnvironment();
        OrtSession.SessionOptions sessionOptions = new OrtSession.SessionOptions();

        // 使用gpu,需要本机按钻过cuda,并修改pom.xml,不安装也能运行本程序
        // sessionOptions.addCUDA(0);
        // 实际项目中,视频识别必须开启GPU,并且要防止队列堆积

        OrtSession session = environment.createSession(model_path, sessionOptions);
        // 输出基本信息
        session.getInputInfo().keySet().forEach(x -> {
            try {
                System.out.println("input name = " + x);
                System.out.println(session.getInputInfo().get(x).getInfo().toString());
            } catch (OrtException e) {
                throw new RuntimeException(e);
            }
        });

        // 加载标签及颜色
        ODConfig odConfig = new ODConfig();
        VideoCapture video = new VideoCapture();

        // 也可以设置为rtmp或者rtsp视频流:video.open("rtmp://192.168.1.100/live/test"), 海康,大华,乐橙,宇视,录像机等等
        // video.open("rtsp://192.168.1.100/live/test")
        // 也可以静态视频文件:video.open("video/car3.mp4");  flv 等
        // 不持支h265视频编码,如果无法播放或者程序卡住,请修改视频编码格式
        video.open(0);  //获取电脑上第0个摄像头
        //video.open("images/car2.mp4"); //不开启gpu比较卡

        //可以把识别后的视频在通过rtmp转发到其他流媒体服务器,就可以远程预览视频后视频,需要使用ffmpeg将连续图片合成flv 等等,很简单。
        if (!video.isOpened()) {
            System.err.println("打开视频流失败,未检测到监控,请先用vlc软件测试链接是否可以播放!,下面试用默认测试视频进行预览效果!");
            video.open("./yolo-common/src/main/java/com/bluefoxyu/video/car3.mp4");
        }

        // 在这里先定义下框的粗细、字的大小、字的类型、字的颜色(按比例设置大小粗细比较好一些)
        int minDwDh = Math.min((int)video.get(Videoio.CAP_PROP_FRAME_WIDTH), (int)video.get(Videoio.CAP_PROP_FRAME_HEIGHT));
        int thickness = minDwDh / ODConfig.lineThicknessRatio;
        double fontSize = minDwDh / ODConfig.fontSizeRatio;
        int fontFace = Imgproc.FONT_HERSHEY_SIMPLEX;

        Mat img = new Mat();

        // 跳帧检测,一般设置为3,毫秒内视频画面变化是不大的,快了无意义,反而浪费性能
        int detect_skip = 4;

        // 跳帧计数
        int detect_skip_index = 1;

        // 最新一帧也就是上一帧推理结果
        float[][] outputData   = null;

        //当前最新一帧。上一帧也可以暂存一下
        Mat image;

        Letterbox letterbox = new Letterbox();
        OnnxTensor tensor;
        // 使用多线程和GPU可以提升帧率,线上项目必须多线程!!!,一个线程拉流,将图像存到[定长]队列或数组或者集合,一个线程模型推理,中间通过变量或者队列交换数据,代码示例仅仅使用单线程
        while (video.read(img)) {
            if ((detect_skip_index % detect_skip == 0) || outputData == null){
                image = img.clone();
                image = letterbox.letterbox(image);
                Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2RGB);

                image.convertTo(image, CvType.CV_32FC1, 1. / 255);
                float[] whc = new float[3 * 640 * 640];
                image.get(0, 0, whc);
                float[] chw = ImageUtil.whc2cwh(whc);

                detect_skip_index = 1;

                FloatBuffer inputBuffer = FloatBuffer.wrap(chw);
                tensor = OnnxTensor.createTensor(environment, inputBuffer, new long[]{1, 3, 640, 640});

                HashMap<String, OnnxTensor> stringOnnxTensorHashMap = new HashMap<>();
                stringOnnxTensorHashMap.put(session.getInputInfo().keySet().iterator().next(), tensor);

                // 运行推理
                // 模型推理本质是多维矩阵运算,而GPU是专门用于矩阵运算,占用率低,如果使用cpu也可以运行,可能占用率100%属于正常现象,不必纠结。
                OrtSession.Result output = session.run(stringOnnxTensorHashMap);

                // 得到结果,缓存结果
                outputData = (float[][]) output.get(0).getValue();
            }else{
                detect_skip_index = detect_skip_index + 1;
            }

            current.clear();
            for(float[] x : outputData){

                ODResult odResult = new ODResult(x);
                String boxName = labels[odResult.getClsId()];
                // 业务逻辑写在这里,注释下面代码,增加自己的代码,根据返回识别到的目标类型,编写告警逻辑。等等
                if(current.containsKey(boxName)){
                    current.put(boxName,current.get(boxName)+1);
                }else{
                    current.put(boxName,1);
                }

                // 画框
                Point topLeft = new Point((odResult.getX0() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY0() - letterbox.getDh()) / letterbox.getRatio());
                Point bottomRight = new Point((odResult.getX1() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY1() - letterbox.getDh()) / letterbox.getRatio());
                Scalar color = new Scalar(odConfig.getOtherColor(odResult.getClsId()));

                Imgproc.rectangle(img, topLeft, bottomRight, color, thickness);
                // 框上写文字

                Point boxNameLoc = new Point((odResult.getX0() - letterbox.getDw()) / letterbox.getRatio(), (odResult.getY0() - letterbox.getDh()) / letterbox.getRatio() - 3);

                // 也可以二次往视频画面上叠加其他文字或者数据,比如物联网设备数据等等
                Imgproc.putText(img, boxName, boxNameLoc, fontFace, 0.7, color, thickness);
                // System.out.println(odResult+"   "+ boxName);

            }

            // 可以自己记录连续帧出现的次数来判断,来避免模型不不准导致的突然误报
            // 判断连续几帧都出现或者都消失,才告警。这样才比较稳定,自己写吧
            StringBuilder info = new StringBuilder();

            for (Map.Entry<String, Integer> entry : last.entrySet()) {
                if(!current.containsKey(entry.getKey())){
                    System.err.println(entry.getValue() +"个 "+entry.getKey()+" 离开了");
                    count.remove(entry.getKey());
                }
            }

            for (Map.Entry<String, Integer> entry : current.entrySet()) {

                int lastCount = last.get(entry.getKey()) == null?0:entry.getValue();
                int currentCount = entry.getValue();
                if((lastCount < currentCount) ){
                    System.err.println(+(currentCount - lastCount)+"个 "+entry.getKey()+" 出现了");
                }
/*              info.append(" ");
                info.append(entry.getKey());
                info.append(" : ");
                info.append(currentCount);*/
            }
            last.clear();
            last.putAll(current);
/*          Point boxNameLoc = new Point(20, 20);
            Scalar color = new Scalar(odConfig.getOtherColor(0));
            Imgproc.putText(img, info.toString(), boxNameLoc, fontFace, 0.6, color, thickness);*/
            //服务器部署:由于服务器没有桌面,所以无法弹出画面预览,主要注释一下代码
            HighGui.imshow("result", img);

            startRequested=true;

            if (stopRequested) {
                break;
            }

            // 多次按任意按键关闭弹窗画面,结束程序
            if(HighGui.waitKey(1) != -1){
                break;
            }
        }

        HighGui.destroyAllWindows();
        video.release();
        stopRequested = false;  // 重置状态以便下一次调用
        startRequested=false;   // 重置状态以便下一次调用
        //System.exit(0);
        System.out.println("该次检测成功~~");

        return "摄像头检测成功";
    }

    class Identify{
        int count;
        int number;
    }

    @PostMapping("/stop")
    public String stopDetection() {
        if (!startRequested){
            return "没有正在进行的检测";
        }
        stopRequested = true;
        System.out.println("执行停止检测");
        return "请求停止检测成功";
    }


}

 关键代码:

Github地址:GitHub - bluefoxyu/yolo-study: 学习yolo+java案例第一次提交

由于在第一次写的时候不知道为什么没有保存好都要发布了内容突然消失,导致第一次写的时候很精细的步骤和过程没有保存,后续由于太晚了就寥寥草草演示完了!

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

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

相关文章

gradio如何实现修改代码后自动重载运行

使用自动重载加速开发 前提条件&#xff1a;本指南要求你了解 Blocks。在阅读本指南之前&#xff0c;请确保你已经阅读了Blocks指南。 本指南涵盖自动重载、在Python IDE中的重载&#xff0c;以及在Jupyter Notebooks中使用Gradio。 为什么需要自动重载&#xff1f; 当你使…

C#归并排序算法

前言 归并排序是一种常见的排序算法&#xff0c;它采用分治法的思想&#xff0c;在排序过程中不断将待排序序列分割成更小的子序列&#xff0c;直到每个子序列中只剩下一个元素&#xff0c;然后将这些子序列两两合并并排序&#xff0c;最终得到一个有序的序列。 归并排序实现原…

蓝牙芯片 vs. 蓝牙模块:如何为蓝牙方案做出最佳选择?

不论您是设计全新的低功耗蓝牙产品&#xff0c;还是升级现有产品&#xff0c;开发者都面临的一个关键的选择&#xff1a;是采用蓝牙芯片还是蓝牙模块呢&#xff1f;作为蓝牙技术领域的资深专家&#xff0c;信驰达将从蓝牙芯片与蓝牙模块的各自优缺点进行分析&#xff0c;帮助您…

通过访存地址获取主存数据的过程

目录 1.根据访存地址在Cache中查找数据 2.如果在Cache中命中 3.如果没有命中 4.数据送CPU 5.做几道题&#xff1a; 主要厘清思路&#xff0c;中间细节需自行补充! 1.根据访存地址在Cache中查找数据 ① 访存地址的结构会根据Cache和主存之间的映射方式不同而改变。映射方式…

【MySql】 mysql的组从复制

mysql的组从复制 配置mastesr [rootmysql-node10 ~]# vim /etc/my.cnf [mysqld] datadir/data/mysql socket/data/mysql/mysql.sock symbolic-links0 log-binmysql-bin server-id1 [rootmysql-node10 ~]# /etc/init.d/mysqld restart #进入数据库配置用户权限 [rootmysql-nod…

方差稳定变换(Variance Stabilizing Transformation)介绍,专业生物学领域统计

介绍 方差稳定变换&#xff08;Variance Stabilizing Transformation&#xff0c;VST&#xff09;是一种统计方法&#xff0c;用于将一个具有异方差性的随机变量&#xff08;即方差随着均值的变化而变化的变量&#xff09;转换为方差相对稳定的变量。这种转换在数据分析和建模…

【网络】TCP协议详解(下)

上文介绍了TCP传输控制协议的报头&#xff0c;并且渗透了TCP保证可靠性的策略&#xff1a;如流量控制、按序到达、确认应答机制以及超时重传。本文继续讲解TCP剩下的协议&#xff0c;剩下俩个大话题&#xff0c;难度都比较麻烦。 本文将介绍TCP协议最常见的三次握手和四次挥手…

腾讯地图SDK Android版开发 7 覆盖物示例1

腾讯地图SDK Android版开发 7 覆盖物示例1 前言界面布局MapMarker类常量成员变量初始值Marker点击事件Marker拖拽事件创建覆盖物移除覆盖物设置属性 MapMarkerActivity类控件响应事件 运行效果图 前言 文本介绍Marker的常用属性、交互和碰撞示例。 示例功能如下&#xff1a; …

【计算机网络】认识端口号 认识传输层协议 认识网络字节序 认识socket套接字

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

收银系统源码—千呼新零售【硬件篇】

连锁店收银系统源码—多商户平台入驻商城已上线-CSDN博客文章浏览阅读1k次。零售行业连锁店收银管理系统多商户入驻本地生活即时零售平台商城https://blog.csdn.net/V15850290240/article/details/141310629 详细介绍请查看上方文章↑↑↑ 详细介绍请查看上方文章↑↑↑ 详细…

[大模型]配置文件-Langchain-Chatchat-V0.3 (1)

文章目录 简述本地配置配置文件model_settings.yaml使用Ollama配置模型配置 使用Xinference配置模型配置修改默认使用的模型 对话基础对话知识库对话 简述 针对Langchain-Chatchat-V0.3版本&#xff0c;对配置文件与模型使用说明&#xff0c;本文建议使用Ollama配合Chatchat使…

用Python实现9大回归算法详解——09. 决策树回归算法

1. 决策树回归的基本概念 决策树回归&#xff08;Decision Tree Regression&#xff09;是一种树状结构的回归模型&#xff0c;通过对数据集进行递归分割&#xff0c;将数据分成更小的子集&#xff0c;并在每个子集上进行简单的线性回归。决策树的核心思想是通过选择特征及其阈…

centos7.9系统安装cloudpods并使用ceph存储(二)

1.ceph安装 1.1 环境准备 配置hosts&#xff1a; $ vim /etc/hosts 10.121.x.x node01设置ssh无密码登录&#xff1a; # ssh-keygen -t rsa # ssh-copy-id -i /root/.ssh/id_rsa node01关闭selinux、firewalld # setenforce 0 # sed -i "s#SELINUXenforcing#SELINUXd…

国自然研究热点、“C位出圈”的类器官研究离不开细胞因子

前 言&#xff1a; 目前&#xff0c;类器官已从基础研究发展至药物开发和精准治疗。在疾病建模、药物开发、肿瘤研究、再生医学、精准医学等领域发展迅速。类器官与体内器官在功能和结构上的高度相似&#xff0c;使其广泛用于发育生物学和疾病建模。传统的2D细胞模型和模式动物…

k8s之Pod对象多种调度方式

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Redis数据库一文入门

Redis 是一个用于存储和管理数据的开源内存数据结构存储系统。它以其高性能和丰富的数据结构支持而闻名&#xff0c;是构建高效、可扩展应用程序的理想选择。本文将带你入门 Redis&#xff0c;并探讨其基本概念、安装步骤和一些常见的使用场景。 什么是 Redis&#xff1f; Re…

疯感工牌的风还是吹到了L4级无人驾驶

俗话说得好&#xff0c;打工人哪有不疯的&#xff1f; 最近你是不是也被“发疯工牌梗”刷屏了 一张张看似情绪稳定的工牌 以独特的方式展现了属于打工人自己的个性 这不&#xff0c;疯感工牌的风也吹到了无人驾驶 无人车也有了属于自己的时尚单品 看看它们都是如何介绍自己的&a…

Wot Design Uni:一个高颜值、轻量化的uni-app组件库,uni-app生态的新宠

一、介绍 wot-design-uni组件库基于vue3Typescript构建&#xff0c;参照wot design的设计规范进行开发&#xff0c;提供70高质量组件&#xff0c;支持暗黑模式、国际化和自定义主题&#xff0c;旨在给开发者提供统一的UI交互&#xff0c;同时提高研发的开发效率。 特性&#x…

新的网络钓鱼方法针对 Android 和 iPhone 用户

关注公众号网络研究观获取更多内容。 ESET 研究人员发现了一种针对 Android 和 iPhone 用户的不常见网络钓鱼活动。 他们分析了一起针对捷克某知名银行客户的网络钓鱼案例。 网络钓鱼流程 这种技术值得注意&#xff0c;因为它会从第三方网站安装钓鱼应用程序&#xff0c;而无…

数据可视化大屏模板-美化图表

Axure作为一款强大的原型设计软件&#xff0c;不仅擅长构建交互式界面&#xff0c;更在数据可视化方面展现出了非凡的创意与实用性。今天&#xff0c;就让我们一起探索Axure设计的几款精美数据可视化大屏模板&#xff0c;感受数据之美。 立体图表的视觉冲击力 Axure的数据可视…