javacv:基于 JavaCPP Presets 的计算机视觉工具库项目

Java interface to OpenCV, FFmpeg, and more

分支6Tags38
文件最后提交记录最后更新时间
27 天前
27 天前
29 天前
1 个月前
7 年前
5 年前
29 天前
11 年前
29 天前
27 天前

JavaCV

Gitter Maven Central Sonatype Nexus (Snapshots) Build Status 商业支持: xscode

简介

JavaCV 借助 JavaCPP Presets 提供的封装,整合了计算机视觉领域研究人员常用的各类库(如 OpenCVFFmpeglibdc1394FlyCaptureSpinnakerOpenKinectlibrealsenseCL PS3 Eye DrivervideoInputARToolKitPlusflandmarkLeptonicaTesseract),并提供实用工具类,以便在 Java 平台(包括 Android)上更便捷地使用这些库的功能。

JavaCV 还提供硬件加速的全屏图像显示(CanvasFrameGLCanvasFrame)、便于在多核处理器上并行执行代码的方法(Parallel)、用户友好的相机与投影仪几何及颜色校准工具(GeometricCalibratorProCamGeometricCalibratorProCamColorCalibrator)、特征点检测与匹配功能(ObjectFinder)、一套实现投影仪-相机系统直接图像对齐的类(主要有 GNImageAlignerProjectiveTransformerProjectiveColorTransformerProCamTransformerReflectanceInitializer)、 blob 分析包(Blobs),以及 JavaCV 类中的其他杂项功能。其中部分类还提供 OpenCL 和 OpenGL 对应的实现,其类名以 CL 结尾或 GL 开头,例如:JavaCVCLGLCanvasFrame 等。

由于目前缺乏完善的文档,若要了解 API 的使用方法,请参考下方的示例用法部分以及示例程序,其中包括两个 Android 示例(FacePreview.javaRecordActivity.java),这些示例程序也可在 samples 目录中找到。您可能还会发现,参考 ProCamCalibProCamTracker 的源代码,以及从《OpenCV2 Cookbook》移植的示例和相关的维基页面会有所帮助。

如果您对代码进行了任何更新或修复,请告知我,以便我将其整合到下一版本中。非常感谢!如果在使用软件时遇到任何问题,欢迎在邮件列表讨论论坛上提问!我确信这个软件还有很多不足之处……

下载

包含 JAR 文件的归档可在 releases 中获取。二进制归档包含适用于 Android、iOS、Linux、Mac OS X 和 Windows 的构建版本。特定子模块或平台的 JAR 文件也可从 Maven Central Repository 单独获取。

要手动安装 JAR 文件,请按照下面 手动安装 部分中的说明进行操作。

我们也可以通过以下方式自动下载并安装所有内容:

  • Maven(在 pom.xml 文件内)
  <dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.14</version>
  </dependency>
  • Gradle(位于 build.gradle.ktsbuild.gradle 文件中)
  dependencies {
    implementation("org.bytedeco:javacv-platform:1.5.14")
  }
  • Leiningen(位于 project.clj 文件内)
  :dependencies [
    [org.bytedeco/javacv-platform "1.5.14"]
  ]
  • sbt(在 build.sbt 文件内)
  libraryDependencies += "org.bytedeco" % "javacv-platform" % "1.5.14"

此操作会下载适用于所有平台的二进制文件,但如果只想获取特定平台的二进制文件,我们可以通过 -D 命令行选项将 javacpp.platform 系统属性设置为 android-armlinux-x86_64macosx-x86_64windows-x86_64 等类似值。有关详细信息,请参阅 JavaCPP Presets 的 README.md 文件。Gradle 用户还可选择使用 Gradle JavaCPP,Scala 用户则可使用 SBT-JavaCV

所需软件

要使用 JavaCV,首先需要下载并安装以下软件:

此外,尽管并非始终必需,但 JavaCV 的某些功能还依赖于:

最后,请确保所有组件的位数相同:32 位和 64 位模块在任何情况下都不能混合使用

手动安装

只需将所有所需的 JAR 文件(opencv*.jarffmpeg*.jar 等)以及 javacpp.jarjavacv.jar 放在类路径中的某个位置即可。以下是针对常见情况的更具体说明:

NetBeans(Java SE 8 或更高版本):

  1. 在 "项目" 窗口中,右键单击项目的 "库" 节点,然后选择 "添加 JAR/文件夹..."。
  2. 找到 JAR 文件,选中它们,然后单击 "确定"。

Eclipse(Java SE 8 或更高版本):

  1. 导航至 "项目" > "属性" > "Java 构建路径" > "库",然后单击 "添加外部 JAR..."。
  2. 找到 JAR 文件,选中它们,然后单击 "确定"。

Visual Studio Code(Java SE 8 或更高版本):

  1. 导航至 "Java 项目" > "引用的库",然后单击 +
  2. 找到 JAR 文件,选中它们,然后单击 "确定"。

IntelliJ IDEA(Android 7.0 或更高版本):

  1. 按照此页面上的说明操作:http://developer.android.com/training/basics/firstapp/
  2. 将所有 JAR 文件复制到 app/libs 子目录中。
  3. 导航至 "文件" > "项目结构" > "app" > "依赖项",单击 +,然后选择 "2 文件依赖项"。
  4. libs 子目录中选择所有 JAR 文件。
  5. 在 AndroidManifest.xml 中添加 android:extractNativeLibs="true"

之后,例如 OpenCV 和 FFmpeg 的包装类就可以自动访问它们所有的 C/C++ API:

示例用法

类定义基本上是将 C/C++ 中的原始头文件移植到 Java,我特意决定尽可能保留原始语法。例如,以下是一个尝试加载图像文件、对其进行平滑处理并将其保存回磁盘的方法:

import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.*;

public class Smoother {
    public static void smooth(String filename) {
        Mat image = imread(filename);
        if (image != null) {
            GaussianBlur(image, image, new Size(3, 3), 0);
            imwrite(filename, image);
        }
    }
}

JavaCV 还在 OpenCV 和 FFmpeg 的基础上提供了辅助类和方法,以促进它们与 Java 平台的集成。以下是一个小型演示程序,展示了最常用的部分:

import java.io.File;
import java.net.URL;
import org.bytedeco.javacv.*;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.indexer.*;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_imgproc.*;
import org.bytedeco.opencv.opencv_calib3d.*;
import org.bytedeco.opencv.opencv_objdetect.*;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_calib3d.*;
import static org.bytedeco.opencv.global.opencv_objdetect.*;

public class Demo {
    public static void main(String[] args) throws Exception {
        String classifierName = null;
        if (args.length > 0) {
            classifierName = args[0];
        } else {
            URL url = new URL("https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml");
            File file = Loader.cacheResource(url);
            classifierName = file.getAbsolutePath();
        }

        // We can "cast" Pointer objects by instantiating a new object of the desired class.
        CascadeClassifier classifier = new CascadeClassifier(classifierName);
        if (classifier == null) {
            System.err.println("Error loading classifier file \"" + classifierName + "\".");
            System.exit(1);
        }

        // The available FrameGrabber classes include OpenCVFrameGrabber (opencv_videoio),
        // DC1394FrameGrabber, FlyCapture2FrameGrabber, OpenKinectFrameGrabber, OpenKinect2FrameGrabber,
        // RealSenseFrameGrabber, RealSense2FrameGrabber, PS3EyeFrameGrabber, VideoInputFrameGrabber, and FFmpegFrameGrabber.
        FrameGrabber grabber = FrameGrabber.createDefault(0);
        grabber.start();

        // CanvasFrame, FrameGrabber, and FrameRecorder use Frame objects to communicate image data.
        // We need a FrameConverter to interface with other APIs (Android, Java 2D, JavaFX, Tesseract, OpenCV, etc).
        OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();

        // FAQ about IplImage and Mat objects from OpenCV:
        // - For custom raw processing of data, createBuffer() returns an NIO direct
        //   buffer wrapped around the memory pointed by imageData, and under Android we can
        //   also use that Buffer with Bitmap.copyPixelsFromBuffer() and copyPixelsToBuffer().
        // - To get a BufferedImage from an IplImage, or vice versa, we can chain calls to
        //   Java2DFrameConverter and OpenCVFrameConverter, one after the other.
        // - Java2DFrameConverter also has static copy() methods that we can use to transfer
        //   data more directly between BufferedImage and IplImage or Mat via Frame objects.
        Mat grabbedImage = converter.convert(grabber.grab());
        int height = grabbedImage.rows();
        int width = grabbedImage.cols();

        // Objects allocated with `new`, clone(), or a create*() factory method are automatically released
        // by the garbage collector, but may still be explicitly released by calling deallocate().
        // You shall NOT call cvReleaseImage(), cvReleaseMemStorage(), etc. on objects allocated this way.
        Mat grayImage = new Mat(height, width, CV_8UC1);
        Mat rotatedImage = grabbedImage.clone();

        // The OpenCVFrameRecorder class simply uses the VideoWriter of opencv_videoio,
        // but FFmpegFrameRecorder also exists as a more versatile alternative.
        FrameRecorder recorder = FrameRecorder.createDefault("output.avi", width, height);
        recorder.start();

        // CanvasFrame is a JFrame containing a Canvas component, which is hardware accelerated.
        // It can also switch into full-screen mode when called with a screenNumber.
        // We should also specify the relative monitor/camera response for proper gamma correction.
        CanvasFrame frame = new CanvasFrame("Some Title", CanvasFrame.getDefaultGamma()/grabber.getGamma());

        // Let's create some random 3D rotation...
        Mat randomR    = new Mat(3, 3, CV_64FC1),
            randomAxis = new Mat(3, 1, CV_64FC1);
        // We can easily and efficiently access the elements of matrices and images
        // through an Indexer object with the set of get() and put() methods.
        DoubleIndexer Ridx = randomR.createIndexer(),
                   axisIdx = randomAxis.createIndexer();
        axisIdx.put(0, (Math.random() - 0.5) / 4,
                       (Math.random() - 0.5) / 4,
                       (Math.random() - 0.5) / 4);
        Rodrigues(randomAxis, randomR);
        double f = (width + height) / 2.0;  Ridx.put(0, 2, Ridx.get(0, 2) * f);
                                            Ridx.put(1, 2, Ridx.get(1, 2) * f);
        Ridx.put(2, 0, Ridx.get(2, 0) / f); Ridx.put(2, 1, Ridx.get(2, 1) / f);
        System.out.println(Ridx);

        // We can allocate native arrays using constructors taking an integer as argument.
        Point hatPoints = new Point(3);

        while (frame.isVisible() && (grabbedImage = converter.convert(grabber.grab())) != null) {
            // Let's try to detect some faces! but we need a grayscale image...
            cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);
            RectVector faces = new RectVector();
            classifier.detectMultiScale(grayImage, faces);
            long total = faces.size();
            for (long i = 0; i < total; i++) {
                Rect r = faces.get(i);
                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);

                // To access or pass as argument the elements of a native array, call position() before.
                hatPoints.position(0).x(x - w / 10     ).y(y - h / 10);
                hatPoints.position(1).x(x + w * 11 / 10).y(y - h / 10);
                hatPoints.position(2).x(x + w / 2      ).y(y - h / 2 );
                fillConvexPoly(grabbedImage, hatPoints.position(0), 3, Scalar.GREEN, CV_AA, 0);
            }

            // Let's find some contours! but first some thresholding...
            threshold(grayImage, grayImage, 64, 255, CV_THRESH_BINARY);

            // To check if an output argument is null we may call either isNull() or equals(null).
            MatVector contours = new MatVector();
            findContours(grayImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
            long n = contours.size();
            for (long i = 0; i < n; i++) {
                Mat contour = contours.get(i);
                Mat points = new Mat();
                approxPolyDP(contour, points, arcLength(contour, true) * 0.02, true);
                drawContours(grabbedImage, new MatVector(points), -1, Scalar.BLUE);
            }

            warpPerspective(grabbedImage, rotatedImage, randomR, rotatedImage.size());

            Frame rotatedFrame = converter.convert(rotatedImage);
            frame.showImage(rotatedFrame);
            recorder.record(rotatedFrame);
        }
        frame.dispose();
        recorder.stop();
        grabber.stop();
    }
}

此外,创建包含以下内容的 pom.xml 文件后:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.bytedeco.javacv</groupId>
    <artifactId>demo</artifactId>
    <version>1.5.14</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.14</version>
        </dependency>

        <!-- Additional dependencies required to use CUDA and cuDNN -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv-platform-gpu</artifactId>
            <version>4.14.0-1.5.14</version>
        </dependency>

        <!-- Optional GPL builds with (almost) everything enabled -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg-platform-gpl</artifactId>
            <version>8.1.2-1.5.14</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>.</sourceDirectory>
    </build>
</project>

将上述源代码放入 Demo.java 中,或者对 samples 目录下的其他类执行类似操作后,我们可以使用以下命令让 Maven 先自动安装所有依赖,然后执行程序:

 $ mvn compile exec:java -Dexec.mainClass=Demo

注意:若出现错误,请确保 pom.xml 文件中的 artifactIdjavacv-platform,而非仅 javacv。例如,javacv-platform 工件会添加所有必要的二进制依赖项。

构建说明

如果上述可用的二进制文件无法满足您的需求,您可能需要从源代码重新构建它们。为此,项目文件已针对以下工具创建:

安装完成后,只需对 JavaCPP、其 Presets 和 JavaCV 调用常规的 mvn install 命令即可。默认情况下,除了 JavaCPP 所需的 C++ 编译器外,不需要其他依赖项。有关更多详细信息,请参考 pom.xml 文件中的注释。

无需手动构建原生库,我们只需为 JavaCV 运行 mvn install,并依赖 CI 构建的快照工件:


项目负责人:Samuel Audet samuel.audet at gmail.com
开发者网站:https://github.com/bytedeco/javacv
讨论组:http://groups.google.com/group/javacv