---
title: "JavaCVとは？OpenCV・FFmpegをJavaで使う導入とFFmpegFrameGrabberの実装【1.5.14対応】"
url: "https://www.issoh.co.jp/tech/details/6779/"
published: 2025-05-16
updated: 2026-10-18
categories: ["Java"]
publisher: "株式会社一創"
---

# JavaCVとは？OpenCV・FFmpegをJavaで使う導入とFFmpegFrameGrabberの実装【1.5.14対応】

JavaCVは、画像処理ライブラリのOpenCVと動画・音声処理のFFmpegを、Javaから呼び出せるようにするオープンソースのライブラリです。C/C++のネイティブライブラリをJARに同梱しているため、OSにOpenCVやFFmpegをインストールしなくても、Mavenに依存を1つ追加すれば動画の読み込みや画像処理を始められます。

この記事では、2026年8月10日公開の最新版JavaCV 1.5.14を対象に、構成、導入、動画の読み書き、OpenCVでの画像処理、よく出るエラーまでを順に解説します。コードはmacOS（x86\_64）上のEclipse Temurin 25とMaven 3.9.16で実行し、出力をそのまま載せています（Gradleの設定例とWebカメラの節は未実行です）。

## まとめ：JavaCVの要点と1.5.14の構成

- JavaCVは、ネイティブライブラリをJavaに橋渡しするJavaCPP、ライブラリごとの橋渡し定義であるJavaCPP Presets、その上の便利クラス群、の3層でできています。普段書くのは `FFmpegFrameGrabber`、`FFmpegFrameRecorder`、`Frame`、`FrameConverter` などの最上層のクラスです。
- 1.5.14に同梱される版はOpenCV 4.14.0、FFmpeg 8.1.2、Tesseract 5.5.3、OpenBLAS 0.3.34です。OpenCV 5.0やFFmpeg 9.0はまだ入っていません。
- 動作要件はJava SE 8以降、Androidは7.0以降です。ライセンスはApache License 2.0とGPL（Classpath例外付き）の選択制で、商用プロジェクトでも使えます。
- `javacv-platform` をそのまま入れると全OS分のネイティブライブラリが入り、今回の計測では139個のJAR・約736MBでした。`-Djavacpp.platform=macosx-x86_64` で絞ると46個・約98MBになります。pom.xmlの `<properties>` に書いただけでは絞れません。
- 標準の `ffmpeg-platform` のH.264エンコーダーはOpenH264で、`crf` は無視されます。libx264を使うには `ffmpeg-platform-gpl` に差し替えます（GPLが適用されます）。
- `grabImage()` が返す `Frame` は毎回同じオブジェクトです。リストに貯めるなら `clone()` が必要です。`grab()` は音声フレームも返すため、映像だけ欲しいときは `grabImage()` を使います。
- 1.4.4以前の記事にある `org.bytedeco.javacpp.opencv_core` は、1.5で `org.bytedeco.opencv.opencv_core`（クラス）と `org.bytedeco.opencv.global.opencv_core`（関数）に分かれました。古いコードは1.5.14ではコンパイルできません。

## JavaCVの3層構造：JavaCPP・Presets・JavaCVの役割分担

JavaCVという名前は最上層のライブラリを指しますが、Mavenで入るのは3層分のJARです。どの層が何をしているかを押さえておくと、依存サイズやエラーの原因を追いやすくなります。

| 層               | 主な成果物                | 役割                             |
| --------------- | -------------------- | ------------------------------ |
| JavaCPP         | `javacpp`            | JNIの生成とネイティブライブラリの読み込み         |
| JavaCPP Presets | `opencv`、`ffmpeg` など | C/C++のAPIをほぼそのままJavaに写したもの     |
| JavaCV          | `javacv`             | Frame・Grabber・Recorderなどの便利クラス |

Presets層のJARは2種類に分かれます。`opencv-4.14.0-1.5.14.jar` のような分類子なしのJARにはJavaのクラスだけが入っており、`opencv-4.14.0-1.5.14-macosx-x86_64.jar` のようにOS名の付いたJARに `.dylib` や `.dll` などのネイティブライブラリが入っています。末尾に `-platform` が付く成果物（`javacv-platform`、`opencv-platform` など）は、このOS別JARをまとめて引き込むためのものです。

JavaCV層の中心は、入力を抽象化した `FrameGrabber`（ファイル、ストリーム、カメラ）、出力を抽象化した `FrameRecorder`、映像と音声を1つの型で運ぶ `Frame`、そして `Frame` をOpenCVの `Mat` やJavaの `BufferedImage` に変換する `FrameConverter` です。OpenCVやFFmpegのC APIを直接触らなくても、この4つで多くの処理が書けます。

JavaCVのREADMEは、OpenCVとFFmpegのほかにTesseract（OCR）、Leptonica、librealsense（Intel RealSense）、libfreenect（Kinect）、FlyCapture、ARToolKitPlusなども対象に挙げています。`javacv-platform` はこれらの多くを依存に含むため、次の章で見るように依存が大きくなります。

## OpenCV公式Javaバインディング・JAVE2との比較と選び方

「JavaでOpenCVを使う」「JavaからFFmpegを使う」方法はJavaCVだけではありません。2026年10月時点の主な選択肢は次のとおりです。

| 方法                  | 最新版                | 扱える範囲           | 導入                          |
| ------------------- | ------------------ | --------------- | --------------------------- |
| JavaCV              | 1.5.14（2026-08-10） | OpenCV＋FFmpeg＋他 | Maven Central               |
| OpenCV公式Javaバインディング | OpenCV 5.0.0       | OpenCVのみ        | Windowsは配布済みバイナリ、その他はソースビルド |
| OpenCV公式Android版    | 5.0.0.1（AAR）       | OpenCVのみ        | `org.opencv:opencv`         |
| openpnp/opencv      | 4.9.0-0（2023-12）   | OpenCVのみ        | Maven Central               |
| JAVE2               | 4.2.0（2026-08-20）  | FFmpegのみ        | Maven Central               |

OpenCVはビルド時にJavaバインディング（JARとJNIライブラリ）を生成できますが、デスクトップ向けのJARはMaven Centralに公式成果物がありません。Maven Centralにある公式の `org.opencv:opencv` はAndroid向けのAARです。デスクトップでMaven経由にしたい場合、以前はopenpnpのビルドがよく使われましたが、2023年12月の4.9.0-0で更新が止まっています。言語にこだわらずOpenCV 5.0を試したいだけなら、pipで入るPython版（`opencv-python`）のほうが早く始められます。使い方は「[OpenCVとは？Pythonでの使い方入門](/tech/details/1344/)」で解説しています。

JAVE2は、JARに同梱したFFmpegの実行ファイルを展開し、外部プロセスとして起動する方式です。「MP4をWebMに変換する」のようなファイル単位の変換なら、JNIを介さないJAVE2も候補です。ただしJAVE2本体はGPLv3なので、配布するアプリではライセンス条件も確認します。4.2.0の同梱FFmpegは9.0.1で、JavaCV 1.5.14の同梱版より新しい版です。

JavaCVを選ぶべきなのは、1フレームずつJavaのコードで画素を処理したい場合と、OpenCVとFFmpegを同じプログラムで組み合わせたい場合です。逆に、変換だけが目的ならJAVE2か `ProcessBuilder` でffmpegコマンドを呼ぶ方法を勧めます。ffmpegコマンドの書き方は「[FFmpegとは？使い方とコマンド一覧](/tech/details/5023/)」にまとめています。

JavaCVを入れてあるなら、ffmpegコマンドを別にインストールする必要もありません。`ffmpeg-platform` のOS別JARには `ffmpeg` と `ffprobe` の実行ファイルが入っており、[JavaCPP PresetsのCHANGELOG](https://github.com/bytedeco/javacpp-presets/blob/master/CHANGELOG.md)によると1.5から `Loader.load()` で呼び出せます。次のコードで、後述の `FFmpegFrameRecorder` で作る5秒のMP4をVP9のWebMに変換できました（終了コード0、出力121,126バイト）。

```
import org.bytedeco.javacpp.Loader;

public class BundledFfmpeg {
    public static void main(String[] args) throws Exception {
        // JARに同梱されたffmpegを展開し、実行ファイルのパスを受け取る
        String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
        Process p = new ProcessBuilder(ffmpeg, "-y", "-hide_banner", "-loglevel", "error",
                "-i", "sample.mp4", "-c:v", "libvpx-vp9", "-b:v", "300k", "out.webm")
                .inheritIO()
                .start();
        System.out.println("exit=" + p.waitFor());
    }
}
```

展開先は `~/.javacpp/cache/` の下で、同梱のffmpegは `ffmpeg version 8.1.2` と表示されました。LGPL版のため、エンコーダーにlibx264は無く、libopenh264・libvpx・libaom・libopusなどが使えます。画像・動画処理を組み込んだ業務システムを一から作る場合は、[フルスクラッチ開発](/service/system/fullscratch/)のように要件に合わせて構成を決める進め方もあります。

## Maven・Gradleでの導入とjavacv-platformの依存サイズ

### Mavenの最小設定とjavacpp.platformの指定方法

必要な依存指定は `javacv-platform` の1つです。以下は既存のpom.xmlの `<project>` 内に追加する断片で、既存の `<properties>` と `<dependencies>` があれば、それぞれに内容を統合します。`javacv` だけを書くとネイティブライブラリが入らず、実行時に後述の `UnsatisfiedLinkError` になります。

```
<properties>
    <maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.14</version>
    </dependency>
</dependencies>
```

この設定のまま依存を解決すると、Android、iOS、Linux、macOS、Windowsの全プラットフォーム分が入ります。JavaCV 1.5.14で実際に取得されたJARの数と合計サイズ（1MB＝1,000,000バイト）を計測した結果が次の表です。OSを絞った行の値はmacosx-x86\_64の場合で、OSによって多少変わります。

| 指定方法                               | JAR数 | 合計サイズ  |
| ---------------------------------- | ---- | ------ |
| 指定なし                               | 139  | 約736MB |
| pom.xmlの `<properties>` に記述        | 139  | 約736MB |
| `-Djavacpp.platform=macosx-x86_64` | 46   | 約98MB  |

[JavaCVのREADME](https://github.com/bytedeco/javacv#downloads)は、`javacpp.platform` を「システムプロパティとして `-D` オプションで」渡すよう書いています。`-platform` 成果物は、このシステムプロパティを条件に有効になるMavenプロファイルで依存を切り替えているため、pom.xmlの `<properties>` に同じ名前の値を書いても効きません。ビルドとCIのコマンドに `-D` を付けます。

```
# 全プラットフォーム分を取得（139 JAR・約736MB）
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt

# macosx-x86_64だけに絞る（46 JAR・約98MB）
mvn dependency:build-classpath -Djavacpp.platform=macosx-x86_64 -Dmdep.outputFile=cp.txt
```

指定できる値は `linux-x86_64`、`linux-arm64`、`macosx-x86_64`、`macosx-arm64`、`windows-x86_64`、`android-arm64` などです。Apple Silicon（M1以降）のMacなら `macosx-arm64` を指定します。

### Gradle JavaCPPプラグインによる対象OSの指定

Gradleには公式のGradle JavaCPPプラグインがあり、`javacppPlatform` で対象を指定します。カンマ区切りで複数のOSを並べられるため、開発機と本番サーバーのOSが違う場合に便利です。以下は[Gradle JavaCPPのREADME](https://github.com/bytedeco/gradle-javacpp)の例をJavaCV 1.5.14に合わせたもので、手元では実行していません。

```
plugins {
    id 'java'
    id 'org.bytedeco.gradle-javacpp-platform' version '1.5.10'
}

// コマンドラインなら -PjavacppPlatform=linux-x86_64,windows-x86_64
ext {
    javacppPlatform = 'linux-x86_64,windows-x86_64'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.bytedeco:javacv-platform:1.5.14'
}
```

このプラグインのリリースは2024年1月の1.5.10が最後で、JavaCV本体（1.5.14）と版番号がずれています。READMEの例は `version "$javacppVersion"` とJavaCVと同じ版を書く形になっているので、1.5.14と書くとプラグインが見つからずに失敗します。

### javacv-platformの不要なネイティブ依存の削減

`-Djavacpp.platform=macosx-x86_64` で絞っても、46個のJARの中にはARToolKitPlus、FlyCapture、libdc1394、libfreenect、librealsense、videoInputのJARが残ります。これらは1.5.9（2023年6月）の版で止まっており、OpenCVとFFmpegしか使わないなら不要です。JavaFXのJAR（`javafx-base`、`javafx-graphics`）も入ります。

依存を `javacv`、`opencv-platform`、`ffmpeg-platform` の3つに組み替えて同じ `-D` を付けると27個・約82MBまで減りました。ただし `javacv` 自体がこれらの成果物のJava部分に依存しているため、ネイティブライブラリが外れるだけで、JARの数はゼロにはなりません。配布サイズをさらに詰めたい場合は、Mavenの `<exclusions>` で個別に外し、実行して確かめます。

### Mavenを使わないJARの入手：javacv-platformのbin.zip

MavenやGradleを使わない環境では、[GitHubのリリースページ](https://github.com/bytedeco/javacv/releases/tag/1.5.14)からJARをまとめたアーカイブを取得できます。1.5.14の `javacv-platform-1.5.14-bin.zip` は1,302,297,073バイト（約1.3GB）で、READMEのとおりAndroid、iOS、Linux、macOS、Windowsの全OS分が入っています。展開したJARを必要なOSの分だけクラスパスに置けば動きますが、OSを絞る作業を手でやることになるため、ビルドツールが使えるなら前述の `-Djavacpp.platform=` を勧めます。

個別のJARはMaven Centralの `org/bytedeco/` 以下にもあります。検索結果に出てくる `com.googlecode.javacv` は、プロジェクトがGoogle Codeにあった頃の旧groupIdです。Maven Centralの検索APIで引くと該当は0件で、Maven Centralにある最も古いJavaCVは `org.bytedeco:javacv` の0.8（2014年4月）でした。新しく書くpom.xmlに旧groupIdを使う理由はありません。

## バージョン対応表：JavaCV 1.5.xと同梱OpenCV・FFmpeg

JavaCVの版を決めると、同梱されるOpenCVとFFmpegの版も決まります。[公式のCHANGELOG](https://github.com/bytedeco/javacv/blob/master/CHANGELOG.md)から直近6版を抜き出しました。

| JavaCV | 公開日        | OpenCV | FFmpeg |
| ------ | ---------- | ------ | ------ |
| 1.5.14 | 2026-08-10 | 4.14.0 | 8.1.2  |
| 1.5.13 | 2026-02-22 | 4.13.0 | 8.0.1  |
| 1.5.12 | 2025-06-30 | 4.11.0 | 7.1.1  |
| 1.5.11 | 2024-11-16 | 4.10.0 | 7.1    |
| 1.5.10 | 2024-01-29 | 4.9.0  | 6.1.1  |
| 1.5.9  | 2023-06-06 | 4.7.0  | 6.0    |

リリースはおおむね半年から1年に1回です。OpenCV 5.0.0（2026年6月）とFFmpeg 9.0（2026年10月時点の最新は9.0.2）はまだ同梱されていないため、これらの新機能が必要な場合はJavaCVの次の版を待つことになります。FFmpeg本体の版ごとの違いは「[FFmpegの最新バージョンは9.0](/tech/details/11447/)」で解説しています。

Presets層のJARは `4.14.0-1.5.14` のように「ライブラリの版-JavaCPPの版」という版番号を持ちます。`opencv-platform` などを個別に書く場合は、後半の `1.5.14` をJavaCVの版と揃えてください。揃っていないとJavaCPPの読み込み処理が合わず、実行時にエラーになります。

1.5.13では、`FFmpegFrameGrabber`・`FFmpegFrameFilter`・`FFmpegFrameRecorder` を閉じたときにLinuxでシステムメモリを返す処理が入りました。1.5.14はこの3クラスの変更はなく、依存の更新と、最低要件をJava SE 8・Android 7.0とする変更が中心です。

### 1.4以前のコードが動かない原因：groupIdとパッケージの変更

検索で見つかるJavaCVの記事には、2019年4月の1.5より前に書かれたものが多く残っています。1.5では、Presets層のgroupIdが `org.bytedeco.javacpp-presets` から `org.bytedeco` に変わりました（[JavaCPP PresetsのCHANGELOG](https://github.com/bytedeco/javacpp-presets/blob/master/CHANGELOG.md)の「Rename groupId to “org.bytedeco”」）。同じ変更でPresetsはJPMSのモジュールに対応し、パッケージも組み替えられています。1.4.4と1.5.14のOpenCVのJARの中身を比べると、次のように移っています。

| 対象      | 1.4.4以前                                | 1.5以降                                       |
| ------- | -------------------------------------- | ------------------------------------------- |
| groupId | `org.bytedeco.javacpp-presets`         | `org.bytedeco`                              |
| Matクラス  | `org.bytedeco.javacpp.opencv_core.Mat` | `org.bytedeco.opencv.opencv_core.Mat`       |
| 関数      | `org.bytedeco.javacpp.opencv_imgproc`  | `org.bytedeco.opencv.global.opencv_imgproc` |

旧形式の `import static org.bytedeco.javacpp.opencv_core.*;` を1.5.14の依存でコンパイルすると、次のエラーで止まります。

```
OldApi.java:1: error: cannot find symbol
import static org.bytedeco.javacpp.opencv_core.*;
                                  ^
  symbol:   class opencv_core
  location: package org.bytedeco.javacpp
```

直し方は機械的で、クラス（`Mat`、`Size` など）は `org.bytedeco.opencv.opencv_core` から、関数（`cvtColor`、`Canny` など）は `org.bytedeco.opencv.global.opencv_imgproc` などから、それぞれimportし直します。FFmpegも同じで、`org.bytedeco.javacpp.avcodec` は `org.bytedeco.ffmpeg.global.avcodec` になります。`org.bytedeco.javacv` パッケージの `FFmpegFrameGrabber` などはそのままです。

## FFmpegFrameGrabberによる動画のフレーム画像抽出

動画を1秒ごとのPNG画像に分割するコードです。`FFmpegFrameGrabber` で動画を開き、`setTimestamp()` でマイクロ秒単位の位置へ移動してから `grabImage()` で映像フレームを1枚取り出します。`Java2DFrameConverter` で `BufferedImage` に変換すれば、あとは標準の `ImageIO` で保存できます。

```
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;

import javax.imageio.ImageIO;
import java.io.File;

public class ExtractFrames {
    public static void main(String[] args) throws Exception {
        File outDir = new File("frames");
        outDir.mkdirs();
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("sample.mp4");
             Java2DFrameConverter conv = new Java2DFrameConverter()) {
            grabber.start();
            System.out.printf("%dx%d %.2ffps frames=%d length=%dus codec=%s%n",
                    grabber.getImageWidth(), grabber.getImageHeight(), grabber.getFrameRate(),
                    grabber.getLengthInFrames(), grabber.getLengthInTime(), grabber.getVideoCodecName());
            long step = 1_000_000L; // 1秒ごと（マイクロ秒）
            for (long t = 0; t < grabber.getLengthInTime(); t += step) {
                grabber.setTimestamp(t);
                Frame frame = grabber.grabImage();
                if (frame == null) break;
                File out = new File(outDir, String.format("frame_%03d.png", t / step));
                ImageIO.write(conv.convert(frame), "png", out);
                System.out.println(out + " ts=" + grabber.getTimestamp());
            }
            grabber.stop();
        }
    }
}
```

5秒・30fps・640×360のH.264動画（後述の `FFmpegFrameRecorder` で作成）に対して実行した結果です。`getLengthInTime()` もマイクロ秒単位で返ります。

```
640x360 30.00fps frames=150 length=5000000us codec=h264
frames/frame_000.png ts=0
frames/frame_001.png ts=1000000
frames/frame_002.png ts=2000000
frames/frame_003.png ts=3000000
frames/frame_004.png ts=4000000
```

コードを `ExtractFrames.java` として保存し、前の章で出力した `cp.txt` をクラスパスにしてコンパイル・実行します。Java 24以降で起動する場合は `--enable-native-access=ALL-UNNAMED` を付けます（理由は後述）。この記事の他のコードも同じ手順で実行しています。このコマンドはmacOS・Linux向けです。Windowsではクラスパスの区切りをセミコロンに変更してください。画面を使わない実行環境では、動画生成・画像保存の例に `-Djava.awt.headless=true` を追加します。

```
javac -cp "$(cat cp.txt)" -d classes ExtractFrames.java
java --enable-native-access=ALL-UNNAMED -cp "classes:$(cat cp.txt)" ExtractFrames
```

### setTimestampとsetVideoTimestampのシーク精度

引数1つの `setTimestamp(long)` は、[FFmpegFrameGrabberのJavadoc](https://bytedeco.org/javacv/apidocs/org/bytedeco/javacv/FFmpegFrameGrabber.html)で「フレームの中身を確かめない（1.4.1より前の）旧方式のシーク」と説明されています。映像フレームを対象に中身を確かめながらシークするのが `setVideoTimestamp(long)` です。前述の5秒のsample.mp4と、音声付きの2秒のav.mp4（次の章で詳しく扱う、同梱ffmpegの `testsrc` と `sine` で作ったMPEG-4映像）で、1.0秒と1.5秒にシークしてから `grabImage()` したフレームの `timestamp` を比べました。

| 動画                   | メソッド                | 1.0秒を指定   | 1.5秒を指定   |
| -------------------- | ------------------- | --------- | --------- |
| sample.mp4（キーフレーム1枚） | `setTimestamp`      | 1,000,000 | 1,500,000 |
| sample.mp4（キーフレーム1枚） | `setVideoTimestamp` | 966,666   | 1,466,666 |
| av.mp4（12枚ごとにキーフレーム） | `setTimestamp`      | 800,000   | 1,200,000 |
| av.mp4（12枚ごとにキーフレーム） | `setVideoTimestamp` | 966,666   | 1,466,666 |

旧方式の `setTimestamp` は、av.mp4では指定より前のキーフレーム（0.8秒・1.2秒）に止まり、最大で0.3秒ずれました。`setVideoTimestamp` はどちらの動画でも指定時刻の1フレーム手前（30fpsで33,334マイクロ秒前）に止まりました。秒単位で位置を合わせたいなら `setVideoTimestamp` を使い、フレーム単位で合わせる必要があるときは、取り出したフレームの `timestamp` が指定時刻に届くまで `grabImage()` を繰り返します。

### grabImageのFrame再利用とcloneによる複製

`grabImage()` と `grab()` が返す `Frame` は、呼ぶたびに新しく作られるわけではありません。3回取り出したフレームをそのままリストに入れたものと、`clone()` してから入れたものを比べると、次のようになりました。

```
same object: true
raw ts: 66666,66666,66666
clone ts: 0,33333,66666
```

そのまま入れた3つは同一のオブジェクトで、タイムスタンプはすべて最後のフレームの値（66,666マイクロ秒）に上書きされています。画素データも同じバッファを指しているため、後で処理すると最後の1枚が3回処理されます。フレームを溜めてから処理する場合は、次のように複製します。

```
List<Frame> frames = new ArrayList<>();
for (int i = 0; i < 3; i++) {
    Frame f = grabber.grabImage();
    if (f == null) break;    // 動画の終端
    frames.add(f.clone());   // clone()しないと3要素とも同じオブジェクトになる
}
// 使い終わったら解放する
for (Frame f : frames) f.close();
```

## Frameクラスのフィールドとgrab()が返す音声フレーム

`Frame` は、映像の1枚・音声の1区切り・字幕などのデータを同じ型で運ぶクラスです。1.5.14のタグの[Frame.javaのソース](https://github.com/bytedeco/javacv/blob/1.5.14/src/main/java/org/bytedeco/javacv/Frame.java)で定義を確認すると、主なフィールドは次のとおりで、いずれもpublicのフィールドとして直接読めます。

| フィールド                        | 型          | 中身                         |
| ---------------------------- | ---------- | -------------------------- |
| `image`                      | `Buffer[]` | 映像の画素（音声フレームではnull）        |
| `imageWidth` ほか              | `int`      | 幅・高さ・深度・チャンネル数・1行の要素数      |
| `samples`                    | `Buffer[]` | 音声サンプル（映像フレームではnull）       |
| `sampleRate`、`audioChannels` | `int`      | サンプリング周波数とチャンネル数           |
| `timestamp`                  | `long`     | マイクロ秒単位の時刻                 |
| `keyFrame`                   | `boolean`  | キーフレームかどうか                 |
| `pictType`                   | `char`     | ピクチャタイプ（`'I'`、`'P'`、`'B'`） |
| `streamIndex`                | `int`      | 元のファイルでのストリーム番号            |
| `opaque`                     | `Object`   | 元になったAVFrameやMatなど         |

`imageDepth` には `Frame.DEPTH_UBYTE`（8）や `Frame.DEPTH_FLOAT`（32）などの定数が入ります。値はビット数で、`DEPTH_BYTE`（-8）のように負の値は符号付きを表します。動画から取った映像フレームは、BGRの `DEPTH_UBYTE`・3チャンネルになります。`getTypes()` を呼ぶと、そのフレームが映像・音声・データのどれを持っているかを `EnumSet<Frame.Type>` で返します。

注意が要るのは、`grab()` が映像だけでなく音声のフレームも順に返すことです。2秒・30fpsのMPEG-4映像と48kHzステレオのAAC音声を持つMP4（前述の同梱ffmpegの `testsrc` と `sine` で作成したav.mp4）を、`grab()` で最後まで読んで数えました（importは `org.bytedeco.javacv.*`）。

```
try (FFmpegFrameGrabber g = new FFmpegFrameGrabber("av.mp4")) {
    g.start();
    int video = 0, audio = 0, keys = 0, n = 0, firstVideo = -1;
    Frame f;
    while ((f = g.grab()) != null) {
        if (f.image != null) {
            if (firstVideo < 0) firstVideo = n;
            video++;
            if (f.keyFrame) keys++;
        }
        if (f.samples != null) audio++;
        n++;
    }
    System.out.printf("grab total=%d video=%d (key=%d) audio=%d firstVideo=#%d%n",
            n, video, keys, audio, firstVideo);
    g.stop();
}
```

```
grab total=154 video=60 (key=5) audio=94 firstVideo=#24
```

154回のうち94回は音声フレームで、最初の映像フレームが返ったのは25回目でした。最初の `grab()` で返った音声フレームを `Java2DFrameConverter` に渡すと、戻り値は `null` でした。映像だけを処理するなら `grabImage()`、音声だけなら `grabSamples()` を使い、`grab()` を使うときは `image` と `samples` のどちらがnullでないかで分岐します。

音声フレームの中身は、`samples` の長さが1、要素が `ShortBuffer` で、残りは2,048個でした。`FFmpegFrameGrabber` の既定では、音声は16ビット整数でチャンネルを交互に並べた形で返るため、1,024サンプル×2チャンネルが1つのバッファに入っています。`timestamp` は21,333または21,334マイクロ秒ずつ進み、1,024÷48,000秒を整数マイクロ秒で表した値と整合しました。映像側の `keyFrame` は60枚中5枚で、OpenH264で書き出した前述の `sample.mp4` では150枚中、先頭の1枚だけでした。前の章のシークの実測で、旧方式の `setTimestamp` がav.mp4で止まった0.8秒と1.2秒は、このキーフレームの位置（0.4秒ごと）と一致します。

## FFmpegFrameRecorderの動画出力とLGPL版・GPL版の違い

Java 2Dで描いた画像を150フレーム並べて、5秒のMP4を書き出すコードです。コーデックは `avcodec.AV_CODEC_ID_H264` のように整数のIDで指定します。

```
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Java2DFrameConverter;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class MakeVideo {
    public static void main(String[] args) throws Exception {
        int w = 640, h = 360, fps = 30, seconds = 5;
        try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("sample.mp4", w, h, 0);
             Java2DFrameConverter conv = new Java2DFrameConverter()) {
            recorder.setFormat("mp4");
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setFrameRate(fps);
            recorder.setVideoOption("crf", "23");
            recorder.start();
            for (int i = 0; i < fps * seconds; i++) {
                BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
                Graphics2D g = img.createGraphics();
                g.setColor(Color.DARK_GRAY);
                g.fillRect(0, 0, w, h);
                g.setColor(Color.ORANGE);
                g.fillOval(i * 3, 120, 120, 120);
                g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 40));
                g.drawString("frame " + i, 20, 60);
                g.dispose();
                recorder.record(conv.convert(img));
            }
            recorder.stop();
        }
    }
}
```

このコードを標準の `ffmpeg-platform` で実行すると、FFmpegが次のログを出しました。

```
Info: avcodec_open2 rejected some options:	Option: crf, value: 23
```

標準の `ffmpeg-platform` はLGPLの範囲でビルドされており、H.264のエンコーダーはlibx264ではなくCisco製のOpenH264です。OpenH264には `crf` というオプションが無いため、指定は無視され、ビットレートは既定の400kb/sになります。依存を `ffmpeg-platform-gpl` に差し替えると、libx264で書き出されます。

```
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>ffmpeg-platform-gpl</artifactId>
    <version>8.1.2-1.5.14</version>
</dependency>
```

同じコードを2つのビルドで実行し、出力をffprobeで比べた結果です。

| 項目          | ffmpeg-platform      | ffmpeg-platform-gpl |
| ----------- | -------------------- | ------------------- |
| H.264エンコーダー | libopenh264          | libx264             |
| プロファイル      | Constrained Baseline | High                |
| `crf` 指定    | 無視される                | 効く                  |
| ビットレート      | 約351kb/s             | 約55kb/s             |
| ファイルサイズ     | 220,727バイト           | 36,907バイト           |

GPL版はFFmpegのビルドに `--enable-gpl`、`--enable-version3`、`--enable-libx264`、`--enable-libx265` を加えたものです。GPL版FFmpegとリンクして一体のプログラムとして配布する場合は、そのプログラム全体をGPLに沿って配布する必要があります（独立したプログラムを同じ媒体に収録するだけなら、この限りではありません）。アプリ全体をGPLに沿って配布できない場合はOpenH264を使い、LGPL版FFmpegの配布条件を満たしたうえで、画質はビットレートの指定（`setVideoBitrate()`）で調整します。社内のサーバー処理のように配布しない用途なら、GPL版で問題ありません。

オプションが無視されたことは、上のように「rejected some options」というログでしか分かりません。1.5.12以降は `FFmpegLogCallback.logRejectedOptions()` も使えます。`setVideoOption()` を書いたら、ログに拒否の行が出ていないかを確認してください。

## FrameからOpenCVのMatへの変換とCanny処理

OpenCVの関数に渡すには、`OpenCVFrameConverter.ToMat` で `Frame` を `Mat` に変換します。動画の2秒目のフレームをグレースケールにし、ガウシアンぼかしをかけてからCanny法でエッジを抽出する例です。

```
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.Mat;

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

public class CannyEdge {
    public static void main(String[] args) throws Exception {
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("sample.mp4");
             OpenCVFrameConverter.ToMat toMat = new OpenCVFrameConverter.ToMat()) {
            grabber.start();
            grabber.setTimestamp(2_000_000L);
            Frame frame = grabber.grabImage();
            if (frame == null) {
                throw new IllegalStateException("映像フレームを取得できませんでした");
            }
            Mat bgr = toMat.convert(frame);
            Mat gray = new Mat();
            Mat edges = new Mat();
            cvtColor(bgr, gray, COLOR_BGR2GRAY);
            GaussianBlur(gray, gray, new org.bytedeco.opencv.opencv_core.Size(5, 5), 0);
            Canny(gray, edges, 50, 150);
            imwrite("edges.png", edges);
            System.out.println("channels=" + bgr.channels() + " edgePixels=" + countNonZero(edges));
            gray.close();
            edges.close();
            grabber.stop();
        }
    }
}
```

実行結果は `channels=3 edgePixels=1353` で、BGRの3チャンネルの画像からエッジ画像 `edges.png` が出力されました。OpenCVの関数名は `org.bytedeco.opencv.global.opencv_imgproc` などのクラスに静的メソッドとしてまとまっており、C++の `cv::Canny` とほぼ同じ引数で呼べます。Cannyの2つのしきい値の決め方は「[エッジ検出とは？Canny法と微分フィルタの使い分け](/tech/details/16854/)」で解説しています。

`Mat` はJavaのヒープの外にメモリを確保します。ガベージコレクションに任せると、ヒープに余裕があるうちは解放されず、プロセスのメモリが増え続けることがあります。ループの中で作る `Mat` は `close()` するか、JavaCPPの `PointerScope` を使ってまとめて解放します。

## Webカメラの取り込みとCanvasFrameでの表示

Webカメラの映像は `OpenCVFrameGrabber` にデバイス番号（通常は0）を渡すか、`FFmpegFrameGrabber` にOSごとの入力形式（Windowsは `dshow`、macOSは `avfoundation`、Linuxは `video4linux2`）を指定して取り込みます。取り込んだ `Frame` を `CanvasFrame` の `showImage()` に渡すとウィンドウに表示され、同時に `FFmpegFrameRecorder` へ `record()` すれば録画になります。この節のカメラ処理は、手元の環境にカメラが無いため実行していません。

`CanvasFrame` はSwingのウィンドウなので、画面の無いサーバーやDockerコンテナ（`java.awt.headless=true` の環境）では使えません。サーバーで動かすときは表示を省き、画像ファイルかストリームに出力してください。

## JavaCVの依存不足・JNI警告・オプション拒否の原因

### UnsatisfiedLinkError: no jniavutilの原因と依存確認

OS別のネイティブJARがクラスパスに無いと、最初にFFmpegのクラスを使った時点で次のエラーになります。OpenCVなら `jniopencv_core` のように名前が変わります。

```
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.bytedeco.ffmpeg.global.avutil
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.UnsatisfiedLinkError: no jniavutil in java.library.path: ...
```

まず確認するのは次の3点です。依存を `javacv` だけにしている、`-Djavacpp.platform=` で指定したOSと実行するOSが違う（例：Macで `linux-x86_64` を指定してビルドし、そのままMacで実行した）、fat JARを作るときにOS別JARが落ちている、の3つです。

### Java 24以降で出るrestricted methodの警告

Java 25でJavaCVを動かすと、処理は成功しても標準エラーに次の警告が出ます。Java 26でも同じ警告が出ることを確認しました。

```
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.bytedeco.javacpp.Loader in an unnamed module (file:...javacpp-1.5.14.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
```

JDK 24で導入されたJEP 472（JNIの使用制限の準備）による警告で、JavaCPPがネイティブライブラリを読み込むときの `System.load` が対象です。警告にあるとおり、`java` の起動オプションに `--enable-native-access=ALL-UNNAMED` を付けると消えます。将来のJDKでは警告ではなくエラーになる予定なので、起動スクリプトとDockerfileには今のうちに入れておきます。Java 17や21で動かしている場合は警告自体が出ません。JVMの起動オプションについては「[Java仮想マシン（JVM）とは？](/tech/details/7151/)」も参照してください。

### avformat\_open\_inputのオプション拒否ログと影響の判別

`FFmpegFrameGrabber` が入力を開く際、入力形式が受け付けない画素形式オプションを渡すと、`Option: pixel_format, value: bgr24` が拒否されたというInfoログが出ることがあります。今回の例ではフレームは正しくBGRの3チャンネルで取れており、処理には影響しませんでした。一方、前の章の `crf` のように、意図した設定が効いていないことを示す場合もあります。どのオプションが拒否されたかを見て判断してください。

## よくある質問

### JavaCVは商用利用できますか？

できます。JavaCV本体はApache License 2.0とGPL（バージョン2以降、Classpath例外付き）から選べ、[LICENSE](https://github.com/bytedeco/javacv/blob/master/LICENSE.txt)には商用プロジェクトでも使えると書かれています。注意が必要なのは同梱するネイティブライブラリ側で、`ffmpeg-platform-gpl` を使うとGPLが適用されます。

### javacvとjavacv-platformの違いは何ですか？

`javacv` はJavaのクラスだけで、ネイティブライブラリを含みません。`javacv-platform` はそれに加えてOS別のネイティブJARを引き込みます。アプリの依存には `javacv-platform` を書き、サイズは `-Djavacpp.platform=` で絞ります。

### Apple Silicon（M1以降）のMacで使えますか？

1.5.14には `macosx-arm64` 用のネイティブJARが用意されています。`javacpp.platform` を指定しなければ自動で含まれ、指定する場合は `macosx-arm64` にします。この記事のコードはIntel Mac（`macosx-x86_64`）で実行したもので、arm64では動かしていません。

### OpenCV 5はJavaCVで使えますか？

2026年10月時点では使えません。最新のJavaCV 1.5.14が同梱するのはOpenCV 4.14.0です。OpenCV 5.0.0の公式成果物はAndroid向けAAR（`org.opencv:opencv`、最新は2026年6月26日公開の5.0.0.1）として出ていますが、デスクトップのJavaから使う場合、Windowsでは公式配布パッケージのJava用ファイルを利用でき、その他のOSではソースビルドが基本です。

### JavaCVでOCRはできますか？

できます。`javacv-platform` にはTesseract 5.5.3のPresetsが含まれており、`org.bytedeco.tesseract` パッケージから呼び出せます。Tesseractの設定（PSM・OEM）や日本語の精度については「[Tesseractとは？](/tech/details/11007/)」で解説しています。

## 関連記事

- [FFmpegとは？使い方とコマンド一覧｜MP4変換・音声抽出・fps変更を9.0で実行確認](/tech/details/5023/)
- [FFmpegの最新バージョンは9.0｜8.1・8.0との違いとインストール・確認方法【2026年10月】](/tech/details/11447/)
- [エッジ検出とは？Canny法と微分フィルタの使い分けをOpenCVの実装で解説](/tech/details/16854/)
- [Java仮想マシン（JVM）とは？仕組み・メモリ構成・JITを実機の出力で解説](/tech/details/7151/)
- [Tesseractとは？読み方・日本語の精度・商用利用とPSM/OEM設定【5.5.3で実測】](/tech/details/11007/)

---

出典: [JavaCVとは？OpenCV・FFmpegをJavaで使う導入とFFmpegFrameGrabberの実装【1.5.14対応】](<https://www.issoh.co.jp/tech/details/6779/>)（株式会社一創）
