開発環境
・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上で指定できるようにする。
以上