(Java)動画で特徴量検出

www.youtube.com

映画とかである動画の中から対象をビビビッと追跡する感じの、やってみました。
JavaOpenCVを使用いたしました。

元々は倉庫の棚卸とかプログラミングでなんとかできないの?みたいな非IT企業のお客さんのご要望に対しての、技術検証として(勝手に)やってた個人的なプロジェクトです。

github.com


目次


画像検出の流れ

おそらくこんな感じでやってるんだろうな、という流れ。

  1. 画像の特徴点(キーポイント)を抽出
  2. 画像の特徴量記述子なるものを計算(特徴点に対して)
  3. 異なる2画像で特徴量記述子をマッチングさせる

OpenCV使えば形にはなっちゃうのがすごいのやら不安やらです。

仕組み

やってることは動画の中から見つけたいロゴ画像を、1フレームごとにAKAZE特徴量を検出してマッチングしているだけです。
計算にパワーが必要なので、ある程度新しめのPCじゃないと厳しいかもしれません。

特徴量記述子の計算

// 比較する画像を読み込み
Mat targetImg = Imgcodecs.imread(TOPLEVEL_DIRECTORY + "\\image\\reiwa.png");
// キーポイント、特徴量記述子
MatOfKeyPoint targetImgKeyPoints = new MatOfKeyPoint();
MatOfFloat targetImgDescriptors = new MatOfFloat();
// キーポイント抽出器、特徴量抽出器
FeatureDetector detector = FeatureDetector.create(FeatureDetector.AKAZE);
DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.AKAZE);
// キーポイント抽出、特徴量抽出
detector.detect(targetImg, targetImgKeyPoints);
extractor.compute(targetImg, targetImgKeyPoints, targetImgDescriptors);

最初にロゴ画像の特徴量記述子は計算しておきます。後々動画の1フレームごとの特徴量記述子とマッチングを行うためです。
思い出しながら書いていて、AKAZE akaze = AKAZE.Create()をやって、akaze.DetectAndCompute()を読んでやればもっとすっきりできるみたいなことがわかりました。
次やる機会があれば…

動画の読み込み

// ビデオファイルの読み込み
VideoCapture videoCapture = new VideoCapture(TOPLEVEL_DIRECTORY + "\\movie\\reiwa.mp4");
// 書き込み先
VideoWriter videoWriter = new VideoWriter(TOPLEVEL_DIRECTORY + "\\result\\reiwa.avi"
     ,VideoWriter.fourcc('M', 'J', 'P', 'G')
    ,videoCapture.get(Videoio.CV_CAP_PROP_FPS)
    ,new Size(videoCapture.get(Videoio.CV_CAP_PROP_FRAME_WIDTH),videoCapture.get(Videoio.CV_CAP_PROP_FRAME_HEIGHT))
    ,true
    );


フレームごとの処理

// フレームごとの処理
Mat frame = new Mat();
while(true){
    if(videoCapture.read(frame)){
        // ここにフレームごとの処理を記述
    }else{
        break;
    }
}

フレーム(画像)へはframeでアクセスできます。
フレームごとに

  • 左上に表示する元のロゴ画像
  • 特徴量記述子の計算
  • ロゴ画像とのマッチング処理
  • 緑色の線分の描画

をやります。


左上のロゴ画像

マッチさせてる感を出したかったので、左上に常に1フレームごとに描画します。

// ROI作成
Mat insertRoi = new Mat(frame,new Rect(0,0,targetImg.cols(),targetImg.rows()));
(中略)
// マッチング終了後にROI挿入。出ないと上のロゴでヒットしすぎる。
targetImg.copyTo(insertRoi);


マッチング

// BFMatcherアルゴリズムでのマッチング器
BFMatcher matcher = BFMatcher.create(BFMatcher.BRUTEFORCE_HAMMING,false);
// マッチング
MatOfDMatch match = new MatOfDMatch();

// knnマッチング
List<MatOfDMatch> listMatch = new ArrayList<>();
MatOfDMatch goodMatch = new MatOfDMatch();
List<DMatch> ratioTestResult = new ArrayList<>();
matcher.knnMatch(targetImgDescriptors,frameDescriptors,listMatch,2);
for(int i = 0; i < listMatch.size(); i++){
    DMatch[] knnMatchVal =  listMatch.get(i).toArray();
    for(int j = 0; j < 1; j++){
        if(knnMatchVal[0].distance < knnMatchVal[1].distance*0.7){
            ratioTestResult.add(knnMatchVal[0]);
        }
    }
}
goodMatch.fromArray(((DMatch[])ratioTestResult.toArray(new DMatch[0])));
match = goodMatch;

BFMatcherknnMatchを行います。
ブルートフォースで上位2点の良かった点を探している、はずです…
ここのマッチング結果の枝切り処理は、クラスの中身とかよくわかってなくて、デバッグ実行で中身を確認しつつ書いていました。

緑色の線分の描画

特徴量記述子のマッチングが終わったら、その結果を描画してやります。

for(int i = 0; i < matchToList.size(); i++){
            // マッチ結果にあるキーポイントのインデックスを取得

            Point moto =  targetImgKeyPoints.toArray()[matchToList.get(i).queryIdx].pt;
            Point target = frameKeyPoints.toArray()[matchToList.get(i).trainIdx].pt;
            // 線分を描画
            Imgproc.line(frame, moto,target,new Scalar(0,255,0),3);
        }
        // フレーム書き込み
        videoWriter.write(frame);

ロゴ画像のキーポイント、フレームのキーポイントを取り出して、それらの間に線分を描画します。


参考

JavaでOpenCVつついてSIFTとSURFしてみる - Ramens-Room
c++ - OpenCV - Two completely different images are having more matches using BFMatcher - Stack Overflow
c++ - OpenCV - Surf Algorithm - Giving lots of false positives - Stack Overflow
Java OpenCV - extracting good matches from knnMatch - Stack Overflow
特徴点のマッチングとホモグラフィによる物体検出 — OpenCV-Python Tutorials
Feature Matching + Homography to find Objects — OpenCV-Python Tutorials 1 documentation
[OpenCV] いまさら局所特徴量で物体検出!? - Qiita
OpenCV: Feature Matching
特徴点のマッチング — OpenCV-Python Tutorials 1 documentation
日記、メモ、その他: 特徴点の対応付け
c++ - OpenCV drawMatches -- queryIdx and trainIdx - Stack Overflow
c++ - How to Access Points location on OpenCV Matcher? - Stack Overflow
How to get pixel coordinates from Feature Matching in OpenCV Python - Stack Overflow
OpenCV 3とPython 3で特徴量マッチング(A-KAZE, KNN) - Qiita
OpenCV の Mat ってやつの話 - あ。
画像マッチング技術の基本原理 - A.N.LAB
OpenCV4Android conversion from MatOfKeyPoint to MatOfPoint2f - OpenCV Q&A Forum
OpenCVで画像の特徴抽出・マッチングを行う - whoopsidaisies's diary
python - I got bad matching results with OpenCV and SIFT. How could I improve it? - Stack Overflow
OpenCVとVisual C++による画像処理と認識(11)----- ORBを用いて特徴点のマッチングを行う -----
image processing - Detecting objects in OpenCV and real time comparison - Stack Overflow
動画を扱う — OpenCV-Python Tutorials 1 documentation
【OpenCV】グレースケール動画を作る【Java】 - <!--親の顔より見た光景-->
【OpenCV】動画の入力と情報の取得【Java】 - <!--親の顔より見た光景-->
OpenCVで動画読み込み&書き込み - 何でもプログラミング
OpenCVのVideoWriterを使って画像から動画を作る。 - 可変ブログ
VideoCapture cannot open video file in Java · Issue #4974 · opencv/opencv · GitHub
OpenCVの環境構築(OpenCV 3.0/3.1) - Build Insider
cv::Matにおけるclone()とcopyTo()の挙動の違い - 聞きかじりめも
画像の一部のみ処理するROIの設定について【OpenCV2.x,3.x】 - Qiita
c++ - Copy an cv::Mat inside a ROI of another one - Stack Overflow
「令和」「令和元年」のイラスト文字 | かわいいフリー素材集 いらすとや
【Java】Eclipseから外部jarライブラリを利用する方法 | のんぽぐ
テックノート – 【Java】カレントディレクトリを取得する方法