学习计算机视觉-javacv

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);
        }

    }
}

出效果

现在站在你面前的是:美利坚合众国一字并肩王,护国公,上柱国铁帽子王,九千岁,大学士兼太子太保太傅,第二巴图鲁.加一品衔,领德州牧兼华盛顿大都督,纽约王、火星领主,加九锡,持假节钺,从龙首功之臣。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值