天空 Blond - yolo
type: channel
link: https://skyblond.info/tag/yolo/
items: 【代码札记】使用ONNX在JVM上调用YoloV8识别鸟类

【代码札记】使用ONNX在JVM上调用YoloV8识别鸟类
type: item
date: 2024-07-01T08:12:00+08:00
description: 深度学习、神经网络、大语言模型，这些都是好东西，可唯独Python不是个好东西。催生于整理录像的需求，本文介绍如何在JVM上使用ONNX在GPU上运行YoloV8图像识别。故事板最近我开始在二楼的平台上喂鸟了。当然了，以我的心态，活的面包虫肯定是接受不了了，所以只能喂些谷物、坚果和烤干的面包虫，吸引来的鸟类也无外乎麻雀、斑鸠、喜鹊。偶尔能看到灰喜鹊和乌鸦，但前者经常被喜鹊打跑，后者来的较晚，坚果早就被喜鹊藏起来了。有一次周末在后院看到了一群大约四五只戴胜，还没来得及拍下照片，人家就跑了，后来再也没见过了（也可能是我起床就十点十一点了，早就过了鸟类早上活动的时间段了）。最初为了观察到底有没有鸟来吃，也是为了给退休的二手手机找个活儿干，加上之前正好有买三脚架送的手机架，还有一个特别不好用的绿联三脚架，这不正好闭环了嘛：把手机架在旁边录像，看看都有哪些鸟来吃。录像用的是IP webcam pro，这个App可以在画面内打上时间戳水印，也可以调整录像质量（码率），灵活性比较高。虽然这个App本身也支持运动检测，但由于是户外，它并不能分辨这种运动是鸟来了，还是大风挂的树枝动。所以我就没有用运动检测，从头到尾录制就是了。这里没提到的一点就是夏天的温度。我的手机是黑色的，人在北京，严格来说算不上太阳直射，但夏天的太阳光威力依旧很大，大约不到半个小时手机就会过热，然后强制结束前台程序。所以一般我都是等下午两点左右开始有树荫了，才把手机放到树荫里。另一个问题是续航，就算是关闭屏幕全靠后台运行，单凭手机的电池坚持不了多久。我一般直接在最开始就挂上充电宝，充电宝没点后再消耗手机的电池。我之前也尝试过先消耗手机的电池，等手机快没电了再挂上充电宝。这样的一个问题就是充电发热，即便手机在阴凉里，如果遇到无风的天气，这种操作也很容易让手机过热。平均算下来，我下午两点录到晚上八点，每天会产生6个小时的录像。我当然当然可以开倍速找出哪里有鸟，然后把对应的片段剪辑出来，但我这不是懒嘛。于是便产生了一个需求：如果能写一个程序帮我自动做这件事呢？野路子™解决方案为什么说用户的话不能全信？如果你按照我的需求走，那可就麻烦大了：首先得想办法从一个视频文件中找出鸟类出现的镜头，找出片段的起点和终点，然后得想办法把这段给弄出来，这里面就涉及到了解码和编码，还得考虑画质会不会有损失，诸如此类的一系列棘手的问题。但如果你追究这个需求的问题，其实归根结底我是想从录像中删掉没有鸟类出现的镜头。这样一来，问题就好办多了：先把长视频切分成时常较短的视频，然后分别对每个视频进行识别，删掉没有鸟类出现的片段，最后再把剩下的片段拼接起来。由于app录制出来的是mp4格式，并且分辨率什么的都是统一的，视频文件本身也具有时间戳，所以这种办法没什么不妥。最关键的是，这种方法完全不需要重新编码，其中切片和拼接的操作可以直接调用ffmpeg的命令行，将流设置为copy，也完全不会损失质量，而且还快。但问题来了：图像识别免不了要调用神经网络。像是Yolo这些模型，全都是Python上的，怎么在jvm上跑起来呢？一开始我看到了opencv里面有个dnn的模块，可以用来加载模型做分类。但是在我尝试的时候发现，我将yoloV8导出成为onnx模型，加载到opencv里，它给不出bounding box的坐标，这不出大事了吗。虽然javacpp这个组给了方便好用的javacv和opencv的原始绑定，可是它用不了啊。ONNX再后来我贼心不死，深入调查了一下ONNX这个东西，发现它这个框架不错。这个框架做的事情类似于提供了一个指令集或者VM的作用，它给出了一些标准化的运算函数，只要其他各种框架能够将自己的模型使用这一套标准化的函数表达出来，那么无论你的模型原本是用什么框架训练的，都可以在其他地方统一进行运算。而正好，ONNX官方也提供了Java的包，让JVM也可以调用ORT（ONNX Runtime）来进行计算，同时支持GPU加速。我选用的模型是YoloV8，这个Yolo系列分了好多，我觉得V8算是表现不错，而且支持文档比较丰富，它的命令行可以按照你给定的分辨率和batch大小直接产生对应的onnx模型。我这里选用的是1920x1080，至于batch大小，这个要看你GPU能吃多少。我的笔记本内置的3070使用yolov8x模型只能给到3，再大的话就会交换到系统内存，导致在传输数据上浪费时间。后来我去租GPU，RTX4090的话同样使用yolov8x，能给到10到12，不过超过10的话瓶颈就不在于显存了，而在于计算上。由于onnx使用的是通用的运算函数，所以它的计算效率并不像pytorch那些模型那么高效，不过jvm上有啥就用啥，能用就不错了，还要什么自行车啊。准备好了模型，下一步就是调用了。YoloV8一开始我…