1
1

More than 1 year has passed since last update.

【Java】OpenCVでUSBカメラでキャプチャした画像を保存する

Last updated at Posted at 2023-06-03

開発環境

・Windows 10 Home
・Eclipse Version: 4.25.0
・Java SE-17
・OpenCV Version 4.6.0

実現したい処理、機能

・PCに接続したUSBカメラで一定の時間ごとに画像をキャプチャし、時刻と撮影番号を追加し、指定したフォルダに保存する。
・GUI上に撮影開始、終了ボタンを設置し、ボタン押下で撮影制御をできるようにする。
・GUI上で解像度、撮影間隔(分)を設定できるようにする。
・GUI上に撮影した画像のプレビューを表示する。

OpenCVのインストール

■参考資料
・ダウンロード
https://opencv.org/releases/

・Windows に OpenCV をインストールし JAR ファイルを生成する
https://neos21.net/blog/2020/06/03-01.html

・EclipseでJava用のOpenCVを使う(for windows)
https://qiita.com/livlea/items/a853c374d6d91b33f5fe

サンプルコード

CameraCapture.java
//import文省略

public class CameraCapture extends JFrame {
    // フレームの幅を示す定数
    private static final int FRAME_WIDTH = 640;
    // フレームの高さを示す定数
    private static final int FRAME_HEIGHT = 480;
    // 撮影間隔を示す定数。デフォルトでは1分
    private static final int CAPTURE_INTERVAL_MINUTES = 1;
    // テキストの色を示す定数
    private static final Scalar FONT_COLOR = new Scalar(255, 255, 255);
    // テキストの太さを示す定数
    private static final int FONT_THICKNESS = 2;

    // VideoCaptureオブジェクト カメラデバイスからのフレームのキャプチャを行う
    private VideoCapture videoCapture;
    // Timerオブジェクト 撮影間隔ごとにフレームの取得と保存を行う
    private Timer captureTimer;
    // 撮影番号を示すカウンター変数
    private int captureCounter;

    // 撮影開始ボタンのJButtonオブジェクト
    private JButton startButton;
    // 撮影終了ボタンのJButtonオブジェクト
    private JButton stopButton;
    // ステータスを表示するJLabelオブジェクト
    private JLabel statusLabel;
    // プレビューを表示するJLabelオブジェクト
    private JLabel previewLabel;
    // 解像度を選択するJComboBoxオブジェクト
    private JComboBox<String> resolutionComboBox;
    // 撮影間隔を入力するJTextFieldオブジェクト
    private JTextField intervalTextField;

    // サポートされる解像度の配列
    private Dimension[] resolutions = {
            new Dimension(640, 480),
            new Dimension(1280, 720),
            new Dimension(1920, 1080)
    };

    // コンストラクタ GUIウィンドウの初期化と構築を行う
    public CameraCapture() {
        setTitle("CameraCapture");
        setSize(FRAME_WIDTH, FRAME_HEIGHT);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        startButton = new JButton("撮影開始");
        stopButton = new JButton("撮影終了");
        statusLabel = new JLabel("Status: Not Started");
        previewLabel = new JLabel();
        resolutionComboBox = new JComboBox<>();
        intervalTextField = new JTextField(String.valueOf(CAPTURE_INTERVAL_MINUTES));

        for (Dimension resolution : resolutions) {
            resolutionComboBox.addItem(resolution.width + "x" + resolution.height);
        }
        resolutionComboBox.setSelectedIndex(0);

        // 撮影開始ボタンのアクションリスナー
        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                startCapture();
                updateButtonStates(true);
                statusLabel.setText("Status: Started");
            }
        });

        // 撮影終了ボタンのアクションリスナー
        stopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                stopCapture();
                updateButtonStates(false);
                statusLabel.setText("Status: Stopped");
            }
        });

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(startButton);
        buttonPanel.add(stopButton);

        JPanel settingPanel = new JPanel();
        settingPanel.add(new JLabel("解像度:"));
        settingPanel.add(resolutionComboBox);
        settingPanel.add(new JLabel("撮影間隔(分):"));
        settingPanel.add(intervalTextField);

        setLayout(new BorderLayout());
        add(buttonPanel, BorderLayout.CENTER);
        add(statusLabel, BorderLayout.SOUTH);
        add(previewLabel, BorderLayout.NORTH);
        add(settingPanel, BorderLayout.EAST);
    }

    // 撮影を開始するメソッド
    private void startCapture() {
        // 解像度を取得
        Dimension resolution = resolutions[resolutionComboBox.getSelectedIndex()];
        // 撮影間隔を取得
        int captureInterval = Integer.parseInt(intervalTextField.getText()) * 60 * 1000;

        videoCapture = new VideoCapture(0); // 0はカメラデバイスのIDで、複数のカメラが接続されている場合に適宜変更する

        // カメラデバイスのオープンに成功しているかどうかをチェック
        if (videoCapture.isOpened()) {
            videoCapture.set(3, resolution.getWidth()); // Width
            videoCapture.set(4, resolution.getHeight()); // Height
            captureCounter = 1;

            // タイマータスクを作成して指定の間隔で撮影を行う
            captureTimer = new Timer();
            captureTimer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    Mat frame = new Mat();
                    videoCapture.read(frame);

                    // 画像に時刻と撮影番号を挿入
                    addTimestampText(frame);

                    // 画像を保存
                    saveImage(frame);

                    // プレビューを更新
                    updatePreview(frame);

                    captureCounter++;
                }
            }, 0, captureInterval);
        } else {
            JOptionPane.showMessageDialog(this, "Failed to open camera.", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    // 撮影を停止するメソッド
    private void stopCapture() {
        if (captureTimer != null) {
            captureTimer.cancel();
            captureTimer = null;
        }

        if (videoCapture != null && videoCapture.isOpened()) {
            videoCapture.release();
            videoCapture = null;
        }
    }

    // フレームに時刻と撮影番号を追加するメソッド
    private void addTimestampText(Mat frame) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String timestamp = dateFormat.format(new Date());
        String text = timestamp + "_" + captureCounter;

        int baseline[] = {0};
        Size textSize = Imgproc.getTextSize(text, Imgproc.FONT_HERSHEY_SIMPLEX, FONT_THICKNESS, FONT_THICKNESS, baseline);
        org.opencv.core.Point textOrg = new org.opencv.core.Point(
                frame.cols() - textSize.width - 10,
                frame.rows() - 10);

        Imgproc.putText(
                frame,
                text,
                textOrg,
                Imgproc.FONT_HERSHEY_SIMPLEX,
                FONT_THICKNESS,
                FONT_COLOR,
                FONT_THICKNESS);
    }

    // 画像を保存するメソッド
    private void saveImage(Mat frame) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String timestamp = dateFormat.format(new Date());
        String fileName = timestamp + "_" + captureCounter + ".jpg";
        String outputPath = "保存先のフォルダパス" + fileName; // 保存先のフォルダパスを適宜変更する

        Imgcodecs.imwrite(outputPath, frame);
    }

    // プレビューを更新するメソッド
    private void updatePreview(Mat frame) {
        BufferedImage image = matToBufferedImage(frame);
        ImageIcon icon = new ImageIcon(image.getScaledInstance(FRAME_WIDTH, FRAME_HEIGHT, Image.SCALE_DEFAULT));
        previewLabel.setIcon(icon);
    }

    // MatオブジェクトをBufferedImageに変換するメソッド
    private BufferedImage matToBufferedImage(Mat frame) {
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if (frame.channels() > 1) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }
        BufferedImage image = new BufferedImage(frame.cols(), frame.rows(), type);
        frame.get(0, 0, ((DataBufferByte) image.getRaster().getDataBuffer()).getData());
        return image;
    }

    // ボタンの状態を更新するメソッド
    private void updateButtonStates(boolean capturing) {
        startButton.setEnabled(!capturing);
        stopButton.setEnabled(capturing);
        resolutionComboBox.setEnabled(!capturing);
        intervalTextField.setEditable(!capturing);
    }

    // メインメソッド
    public static void main(String[] args) {
        // OpenCVの初期化
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                CameraCapture gui = new CameraCapture();
                gui.setVisible(true);
            }
        });
    }
}

今後実装したい機能

・動画形式でカメラプレビューを表示する。
・解像度に合わせて、挿入する時刻と撮影番号の大きさを調整する。
・保存先をGUI上で指定できるようにする。

以上

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1