JavaCV 可能会让人想到"CV工程师"的梗,其实是一个用于计算机视觉任务的 Java 库,它封装了 OpenCV 和其他多媒体框架的功能,使得开发者可以在 Java 环境中方便地进行图像处理和视频分析。以下是关于 JavaCV 的几个要点:
- 功能:支持图像处理、视频捕捉、特征检测、对象识别等。
- 兼容性:与多种操作系统(如 Windows、Linux、MacOS)兼容。
- 依赖管理:通常通过 Maven 或 Gradle 等构建工具引入 JavaCV 依赖。
- 社区支持:拥有活跃的社区和丰富的文档资源。
学习目标:
1、调用摄像头
2、视频处理
3、流媒体服务
4、简单的图像识别
基础思路:
调用摄像头,获取到一段视频流,视频由帧组成,将视频抽帧为图片,加载模型检测识别,将检测后的帧写到窗口上,或者流媒体服务器上(有关于流媒体服务, 后期出个续集帖子)
基本概念:
ffmpeg:ffmpeg 是一个非常强大的开源多媒体框架,用于处理音视频数据。它包括了一套完整的工具和库,可以用于视频和音频的录制、转换、流媒体传输等多种任务。
FrameGrabber:FrameGrabber 是 JavaCV 库中的一个类,用于从视频源(如摄像头、视频文件等)捕获视频帧。
CanvasFrame:CanvasFrame 是 JavaCV 库中的一个类,用于在图形用户界面(GUI)中显示图像或视频帧。它基于 Java 的 Swing 框架,提供了一个简单的窗口组件,可以用来显示从摄像头捕获的视频帧、处理后的图像等。
CascadeClassifier:CascadeClassifier 是 OpenCV 库中的一个类,用于实现级联分类器(Cascade Classifier)。级联分类器主要用于对象检测任务,特别是人脸检测、眼睛检测等。它基于 Viola-Jones 物体检测框架,通过训练大量的正负样本,生成一个级联分类器模型,用于快速准确地检测图像中的特定对象。
Net:Net 是 OpenCV 库中的一个类,用于加载和使用深度学习模型。它主要用于执行前向传播(forward pass),即通过输入数据得到模型的预测结果。Net 类支持多种深度学习框架的模型,如 Caffe、TensorFlow、Darknet 等
Frame:Frame 表示一帧图像数据,通常用于存储从摄像头捕获的视频帧 captureFrame;
IplImage:IplImage 表示一帧图像数据,OpenCV的图像数据结构
Mat:Mat 表示 OpenCV 的矩阵数据结构,用于存储图像数据
上代码,注释写得比较详细,复制下来在本地走读一下
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacpp.indexer.Indexer;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_dnn.Net;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import javax.swing.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_dnn.blobFromImage;
import static org.bytedeco.opencv.global.opencv_dnn.readNetFromCaffe;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
/**
* @author 器轩
* @create 2024-12-10 13:59
*/
public class javacvdemo {
public static void main(String[] args) throws Exception {
// ==================== 初始化 ===================
// 设置ffmepg日志级别
avutil.av_log_set_level(avutil.AV_LOG_INFO);
// set()方法调用通常用于设置 FFmpeg 的日志回调函数
FFmpegLogCallback.set();
// 初始化帧抓取器,传参摄像头位置,第一个摄像头0,第二个摄像头1,以此类推
FrameGrabber grabber = new OpenCVFrameGrabber(0);
// 摄像头有可能有多个分辨率,这里可以指定宽高,也可以不指定反而调用grabber.getImageWidth去获取,
grabber.setImageWidth(1280);
grabber.setImageHeight(720);
// 开启抓取器,会有异常,就不捕获了,直接抛出
grabber.start();
// 用于显示从摄像头捕获的视频帧
/**
* "调用摄像头": 这是窗口的标题,显示在窗口的标题栏上。
* CanvasFrame.getDefaultGamma(): 获取默认的伽马值。
* grabber.getGamma(): 获取摄像头捕获设备的伽马值。
* CanvasFrame.getDefaultGamma() / grabber.getGamma(): 计算一个比例值,用于调整显示的图像亮度。
*/
CanvasFrame previewCanvas = new CanvasFrame("调用摄像头", CanvasFrame.getDefaultGamma() / grabber.getGamma());
// 设置窗口关闭操作的行为,JFrame.EXIT_ON_CLOSE:当用户点击窗口的关闭按钮时,程序将退出并终止整个应用程序。
previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口始终位于其他窗口之上。
previewCanvas.setAlwaysOnTop(true);
// ==================== 加载模型 ===================
// 根据模型文件实例化分类器 这里用的开源的人脸检测器
CascadeClassifier classifier = new CascadeClassifier("E:\\temp\\opencv\\gender\\haarcascade_frontalface_alt.xml");
// 实例化推理性别的神经网络 这里用的是开源的性别识别模型
Net cnnNet = readNetFromCaffe("E:\\temp\\opencv\\gender\\deploy.prototxt", "E:\\temp\\opencv\\gender\\gender_net.caffemodel");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 窗口持续 60 秒,
long endTime = System.currentTimeMillis() + 1000L * 60;
// 两帧输出之间的间隔时间,默认是1000除以帧率,假设一秒钟15帧,那么两帧间隔就是(1000/15)毫秒
int interVal = 1000/30;
// 水印在图片上的位置
org.bytedeco.opencv.opencv_core.Point point = new org.bytedeco.opencv.opencv_core.Point(15, 35);
// 表示一帧图像数据,通常用于存储从摄像头捕获的视频帧
Frame captureFrame;
// 表示一帧图像数据,OpenCV的图像数据结构
IplImage img;
// 表示 OpenCV 的矩阵数据结构,用于存储图像数据
Mat mat;
OpenCVFrameConverter.ToIplImage openCVConverter = new OpenCVFrameConverter.ToIplImage();
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
// 超过指定时间就结束循环
while (System.currentTimeMillis()<endTime) {
// grab() 方法 取一帧
captureFrame = grabber.grab();
if (null==captureFrame) {
break;
}
// 将帧对象转为IplImage对象
img = openCVConverter.convert(captureFrame);
// 镜像翻转
cvFlip(img, img, 1);
// IplImage转mat
mat = new Mat(img);
// 在图片上添加水印,水印内容是当前时间,位置是左上角
opencv_imgproc.putText(mat,
simpleDateFormat.format(new Date()),
point,
opencv_imgproc.CV_FONT_VECTOR0,
0.8,
new Scalar(0, 200, 255, 0),
1,
0,
false);
Frame frame = openCVConverter.convert(mat);
// 原始帧先交给检测服务处理,这个处理包括物体检测,再将检测结果标注在原始图片上,然后转换为帧返回
// 由帧转为Mat
Mat grabbedImage = converter.convert(frame);
// 灰度Mat,用于检测
Mat grayImage = new Mat(grabbedImage.rows(), grabbedImage.cols(), CV_8UC1);
// 当前图片转为灰度图片
cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);
// 存放检测结果的容器
RectVector objects = new RectVector();
// 开始检测
classifier.detectMultiScale(grayImage, objects);
// 检测结果总数
long total = objects.size();
if (total<1) {
// 如果没有检测到结果,就用原始帧返回
previewCanvas.showImage(frame);
}else {
int pos_x;
int pos_y;
Mat faceMat;
//推理时的入参
Mat inputBlob;
// 推理结果
Mat prob;
// 如果有检测结果,就根据结果的数据构造矩形框,画在原图上
for (long i = 0; i < total; i++) {
Rect r = objects.get(i);
// 人脸对应的Mat实例(注意:要用彩图,不能用灰度图!!!)
faceMat = new Mat(grabbedImage, r);
// 缩放到神经网络所需的尺寸
resize(faceMat, faceMat, new Size(256, 256));
// 归一化
normalize(faceMat, faceMat, 0, Math.pow(2, frame.imageDepth), NORM_MINMAX, -1, null);
// 转为推理时所需的的blob类型
inputBlob = blobFromImage(faceMat);
// 为神经网络设置入参
cnnNet.setInput(inputBlob, "data", 1.0, null); //set the network input
// 推理
prob = cnnNet.forward("prob");
// 根据推理结果得到在人脸上标注的内容
Indexer indexer = prob.createIndexer();
// 比较两种性别的概率,概率大的作为当前头像的性别
String lable = indexer.getDouble(0,0) > indexer.getDouble(0,1) ? "male" : "female";
// 人脸标注的横坐标
pos_x = Math.max(r.tl().x()-10, 0);
// 人脸标注的纵坐标
pos_y = Math.max(r.tl().y()-10, 0);
// 给人脸做标注,标注性别
putText(grabbedImage, lable, new Point(pos_x, pos_y), FONT_HERSHEY_PLAIN, 1.5, new Scalar(0,255,0,2.0));
// 给人脸加边框时的边框位置
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
// 给人脸加边框
rectangle(grabbedImage, new Point(x, y), new Point(x + w, y + h), Scalar.RED, 1, CV_AA, 0);
}
// 释放检测结果资源
objects.close();
// 将标注过的图片转为帧,返回
Frame detectedFrame = converter.convert(grabbedImage);
// 预览窗口上显示的帧是标注了检测结果的帧
previewCanvas.showImage(detectedFrame);
}
// 适当间隔,让肉感感受不到闪屏即可
Thread.sleep(interVal);
}
}
}
出效果
现在站在你面前的是:美利坚合众国一字并肩王,护国公,上柱国铁帽子王,九千岁,大学士兼太子太保太傅,第二巴图鲁.加一品衔,领德州牧兼华盛顿大都督,纽约王、火星领主,加九锡,持假节钺,从龙首功之臣。