スマートビデオプレーヤーまたは単なるジェスチャー認識

はじめに



この記事では、ジェスチャ認識に焦点を当てます。 この情報を入力する方法は人間にとってより便利であるため、このトピックは今日非常に重要であると考えています。 YouTubeでは、認識、オブジェクトトラッキングに関する多くのビデオを見ることができます。ハブには、このトピックに関する記事もあります。そこで、私は自分自身で有用で必要なことを実験して実行することにしました。 特に外国語で映画を見るときは、マウスを取り上げてこのスライダーを見つけて少し前または少し後ろに巻き戻すのが非常に面倒なので、ジェスチャーの制御に使用できるビデオプレーヤーを作成することにしました。 。



この記事では、主にジェスチャ認識の実装方法について説明し、ビデオプレーヤーについてのみ説明します。



それで、私たちは何が欲しいのでしょうか?



ジェスチャを使用して次のコマンドを提供できるようにしたいと思います。

  1. 早送り/巻き戻し
  2. 一時停止/続行
  3. サウンドの追加/減少
  4. 次/前のトラック。


次のアクションとジェスチャーを比較します。

  1. 左から右/右から左
  2. 中心から右への動き
  3. 下から上へ/上から下への移動
  4. 中心から右下/中心から左下への移動


ウェブカメラを通してジェスチャーを観察します。 私のラップトップには0.3メガピクセルのカメラがあります。 それは弱いですが、良い照明があれば、そのような目的にも使用できます。 ただし、外部USB verカメラを使用します。 ジェスチャは、色でフィルタリングして背景から選択するため、単色の棒で表示されます。 たとえば、そのようなオブジェクトとして普通の鉛筆を使用します。 もちろん、これはオブジェクトを認識する最良の方法ではありませんが、写真内のオブジェクトではなく、ジェスチャー(動き)を認識することに焦点を当てたいと思いました。



ツール



Qt framework 5.2を開発環境として使用します。 Webカメラからのビデオストリームを処理するには、OpenCV 4.6を使用します。 GUIは完全にQMLになり、認識ユニットはC ++になります。



どちらもオープンソースのツールであり、どちらもクロスプラットフォームです。 Linux用のプレーヤーを開発していましたが、他のプラットフォームに転送できます。目的のプラットフォームのQtサポートを使用してOpenCVをコンパイルし、プレーヤーを再コンパイル、再ビルドするだけです。 プレーヤーをWindowsに転送しようとしましたが、OpenCVをQtサポート付きでコンパイルできませんでした。 誰が試して、誰が成功するか、マニュアルまたはバイナリを共有してください。



プレーヤーの構造



次の図は、プレーヤーの構造を示しています。 プレーヤーは2つのストリームで動作します。 メインストリームはGUIとビデオプレーヤーです。 インターフェイスがハングしたりビデオが再生されたりするのを防ぐために、認識ユニットは別のストリームに配置されます。 インターフェイスはQMLで、プレイヤーロジックはJSで、認識ブロックはC ++で記述しました(誰もが理由を知っています)。 プレーヤーは、信号とスロットを使用して認識ユニットと「通信」します。 アプリケーションを2つのスレッドに分離しやすくするために、認識クラスのラッパーを作成しました。 実際、ラッパーはメインスレッドにあります(つまり、示されているとおりではありません)。 ラッパーは、認識クラスのインスタンスを作成し、新しい追加のストリームに配置します。 実際、プレーヤーに関するすべてのことは、認識について話し続け、コードを提供します。







認識



認識するために、フレームを収集し、確率論の方法を使用して処理します。 1秒あたり20フレームを収集します(Webカメラではこれ以上許可しません)。 それぞれ10フレームを処理します。



アルゴリズム:

  1. Webカメラからフレームを取得し、フィルターに送信します。
  2. フィルターは、黒の背景に白い長方形の形の鉛筆のみを表示するバイナリイメージを返します。
  3. バイナリイメージはアナライザーに送信され、そこで鉛筆の頂点が計算されます。 一番上のピークがアレイに入力されます。
  4. 配列が10要素のサイズに達すると、この配列は確率分析に送られ、そこで数値のペアのシーケンスが最小二乗法で分析されます。
  5. 分析がコマンドを認識した場合、このコマンドはビデオプレーヤーに送信されます。




基本的な認識機能は3つだけです。



次の機能は、ジェスチャーコントロールが有効になっている場合にカメラを監視します。



void MotionDetector::observCam() { m_cap >> m_frame; //     filterIm(); //    detectStick(); // ,      drawStick(m_binIm); //    showIms(); //    }
      
      







認識機能は次のとおりです。



 void MotionDetector::detectStick() { m_contours.clear(); cv::findContours(m_binIm.clone(), m_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); if (m_contours.empty()) return; bool stickFound = false; for(int i = 0; i < m_contours.size(); ++i) { //    ,    if(cv::contourArea(m_contours[i]) < m_arAtThreshold) continue; //    m_stickRect = cv::minAreaRect(m_contours[i]); cv::Point2f vertices[4]; cv::Point top; cv::Point bottom; m_stickRect.points(vertices); if (lineLength(vertices[0], vertices[1]) > lineLength(vertices[1], vertices[2])){ top = cv::Point((vertices[1].x + vertices[2].x) / 2., (vertices[1].y + vertices[2].y) / 2.); bottom = cv::Point((vertices[0].x + vertices[3].x) / 2., (vertices[0].y + vertices[3].y) / 2.); } else{ top = cv::Point((vertices[0].x + vertices[1].x) / 2., (vertices[0].y + vertices[1].y) / 2.); bottom = cv::Point((vertices[2].x + vertices[3].x) / 2., (vertices[2].y + vertices[3].y) / 2.); } if (top.y > bottom.y) qSwap(top, bottom); m_stick.setTop(top); m_stick.setBottom(bottom); stickFound = true; } //   switch (m_state){ case ST_OBSERVING: if (!stickFound){ m_state = ST_WAITING; m_pointSeries.clear(); break; } m_pointSeries.append(QPair<double, double>(m_stick.top().x, m_stick.top().y)); if (m_pointSeries.size() >= 10){ m_actionPack = m_pSeriesAnaliser.analize(m_pointSeries); if (!m_actionPack.isEmpty()){ emit sendAction(m_actionPack); } m_pointSeries.clear(); } break; case ST_WAITING: m_state = ST_OBSERVING; break; } }
      
      







ここで最小二乗法について読むことができます 。 次に、どのように実現したかを示します。 以下は、確率的シリーズアナライザーです。



 bool SeriesAnaliser::linerCheck(const QVector<QPair<double, double> > &source) { int count = source.size(); //    2  ,   . QVector<double> x(count); QVector<double> y(count); for (int i = 0; i < count; ++i){ x[i] = source[i].first; y[i] = source[i].second; } double zX, zY, zX2, zXY; // z -   . zX -  x-  .. QVector<double> yT(count); //   zX = 0; for (int i = 0; i < count; ++i) zX += x[i]; zY = 0; for (int i = 0; i < count; ++i) zY += y[i]; zX2 = 0; for (int i = 0; i < count; ++i) zX2 += x[i] * x[i]; zXY = 0; for (int i = 0; i < count; ++i) zXY += x[i] * y[i]; //    double a = (count * zXY - zX * zY) / (count * zX2 - zX * zX); double b = (zX2 * zY - zX * zXY) / (count * zX2 - zX * zX); //   y for (int i = 0; i < count; ++i) yT[i] = x[i] * a + b; double dif = 0; for (int i = 0; i < count; ++i){ dif += qAbs(yT[i] - y[i]); } if (a == 0) a = 10; #ifdef QT_DEBUG qDebug() << QString("%1x+%2").arg(a).arg(b); qDebug() << dif; #endif //   > vBorder,  ,  ,   //    epsilan,  ,  ,   //  oblMovMin < a < oblMovMax,  ,  ,   //    0.6,  ,  ,   //  a < horMov,  ,  ,  . int vBorder = 3; int epsilan = 50; double oblMovMin = 0.5; double oblMovMax = 1.5; double horMov = 0.2; //    ,   if (qAbs(a) < vBorder && dif > epsilan) return false; //   double msInFrame = 1000 / s_fps; double dTime = msInFrame * count; // ms double dDistance; // px double speed = 0; /*px per ser*/ if (qAbs(a) < vBorder) dDistance = x[count - 1] - x[0]; //    else dDistance = y[count -1] - y[0]; speed = dDistance / dTime; //px per //    ,  if (qSqrt(qPow(x[0] - x[count - 1], 2) + qPow(y[0] - y[count - 1], 2)) < 15){ return false; } //    . if (speed > 0.6) return false; //   if (qAbs(a) > oblMovMin && qAbs(a) < oblMovMax){ //  if (a < 0){ //   s_actionPack = "next"; } else{ if (speed < 0) s_actionPack = "play"; else //   s_actionPack = "previous"; } } else if (qAbs(a) < horMov) { s_actionPack = QString("rewind %1").arg(speed * -30000); } else if (qAbs(a) > vBorder){ s_actionPack = QString("volume %1").arg(speed * -1); } else return false; return true; }
      
      







次のコードスニペットは、認識されたアクション(ビデオプレーヤー側)を実行します。



  function executeComand(comand){ var comandList = comand.split(' '); console.log(comand); switch (comandList[0]) { case "next": nextMedia(); break; case "previous": previousMedia(); break; case "play": playMedia(); break; case "rewind": mediaPlayer.seek(mediaPlayer.position + Number(comandList[1])); break; case "volume": mediaPlayer.volume += Number(comandList[1]); break; default: break; } }
      
      







はい、私はほとんど忘れていました、鉛筆は色で背景から際立っており、これはどこかに設定する必要があります。 次のように問題を解決しました。







つまり、奉献のために色を調整し、この色をメニューに保存して、調整せずにすぐに使用できるようにします。



私が得た結果は次のとおりです。





おわりに



これで、ビデオプレーヤーは非常に簡単なジェスチャーを認識できるようになりました。 私の意見では、プレーヤーで最も成功した便利なことは、巻き戻し/前進ジェスチャーです。 そして、このチームが最もうまく安定して機能します。 映画を見るためにジェスチャコントロールを調整する必要がありますが、少し巻き戻すためにマウスを探す必要はありません。



PS: 気にする人 、ここにSmartVPのソースがあります。

PPS:私は多くの色を再分割しました、オレンジが最高であることが判明しました(速い動きに耐える)。



All Articles