質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.01%

  • C++

    2936questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • Ubuntu

    1088questions

    Ubuntuは、Debian GNU/Linuxを基盤としたフリーのオペレーティングシステムです。

  • OpenCV

    830questions

    OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

  • VMware

    195questions

    VMwareとは、 ハードウェアで動作するOS上で仮想マシンを作成、実行するソフトウェアです。 Windows上でUNIX系OSを動作させたり、他のOS上で別の仮想OSを動作することが可能です。

OpenCVを使った笑顔検出での問題

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 578

kotetu

score 9

前提・実現したいこと

OpenCVで元からあるサンプルコード「smiledetect.cpp」を用いて笑顔検出のシステムを作っています。
検出機能を実装中に以下の問題が発生しました。

発生している問題・状況

問題:笑顔検出されていたら四角で囲むはずが、囲まれていない。

状況:リアルタイムで検出している顔だけが移っている状態。
   画像に笑顔検出のプログラムを実行したところ、画像のようになった。
[画像での笑顔検出](https://imgur.com/a/npC1H)

該当のソースコード

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;
static void help()
{  
cout<<"Program build!!!! "<<CV_VERSION<< "\n"<<endl;
}
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
                    CascadeClassifier& nestedCascade,
                    double scale, bool tryflip );
string cascadeName;
string nestedCascadeName;
int main(int argc, const char** argv)
{
  VideoCapture capture;
  Mat frame, image;
  string inputName;
  bool tryflip;
  help();
  CascadeClassifier cascade, nestedCascade;
  double scale;
  //カメラからのビデオキャプチャを初期化する
  CvCapture*videoCapture = cvCreateCameraCapture(1);
  if(videoCapture ==NULL)
    {
      return -1;
    }
  cv::CommandLineParser parser(argc, argv,
                   "{help h||}{scale|1|}"
                   "{cascade|../../data/haarcascades/haarcascade_frontalface_alt.xml|}"
                   "{smile-cascade|../../data/haarcascades/haarcascade_smile.xml|}"
                   "{try-flip||}{@input||}");
  if (parser.has("help"))
    {
      help();
      return 0;
    }
  //ウィンドウを作成する
  char windowName[]="camera";
  cvNamedWindow(windowName,CV_WINDOW_AUTOSIZE);
  while(cvWaitKey(1)==-1)
    {
      //カメラから1フレーム習得する
      IplImage*image=cvQueryFrame(videoCapture);
      //ウィンドウに画像を表示する
      cvShowImage(windowName,image);
    }
  cascadeName = parser.get<string>("cascade");
  nestedCascadeName = parser.get<string>("smile-cascade");
  tryflip = parser.has("try-flip");
  inputName = parser.get<string>("@input");
  scale = parser.get<int>("scale");
  if (!parser.check())
    {
      help();
      return 1;
    }
  if (scale < 1)
    scale = 1;
  if( !cascade.load( cascadeName ) )
    {
      cerr << "ERROR: Could not load face cascade" << endl;
      help();
      return -1;
    }
  if( !nestedCascade.load( nestedCascadeName ) )
    {
      cerr << "ERROR: Could not load smile cascade" << endl;
      help();
      return -1;
    }
  if( inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1) )
    {
      int c = inputName.empty() ? 0 : inputName[0] - '0' ;
      if(!capture.open(c))
            cout << "Capture from camera #" <<  c << " didn't work" << endl;
    }
  else if( inputName.size() )
    {
      if(!capture.open( inputName ))
    cout << "Could not read " << inputName << endl;
    }
  if( capture.isOpened() )
    {
      cout << "Video capturing has been started ..." << endl;
      cout << endl << "NOTE: Smile intensity will only be valid after a first smile has been detected" << endl; 
      while(1)
        {
      capture >> frame;
      if( frame.empty() )
        break;  
      Mat frame1 = frame.clone();
      detectAndDraw( frame1, cascade, nestedCascade, scale, tryflip );
      char c = (char)waitKey(10);
      if( c == 27 || c == 'q' || c == 'Q' ){
        break; }
      //else if(c==32|| c=='s'||c=='S'){
      //cv::imwrite("smile.png", frame); }
        }
    }
  else
    {
      cerr << "ERROR: Could not initiate capture" << endl;
      help();
      return -1;
    }
  //ビデオキャプチャを開放する
  cvReleaseCapture(&videoCapture);
  //ウィンドウを破棄する
  cvDestroyWindow(windowName);
  return 0;
 }
 void detectAndDraw( Mat& img, CascadeClassifier& cascade,
             CascadeClassifier& nestedCascade,
             double scale, bool tryflip)
 {
   vector<Rect> faces, faces2;
   const static Scalar colors[] =
    {
      Scalar(255,0,0),
      Scalar(255,128,0),
      Scalar(255,255,0),
      Scalar(0,255,0),
      Scalar(0,128,255),
      Scalar(0,255,255),
      Scalar(0,0,255),
      Scalar(255,0,255)
    };
    Mat gray, smallImg;
    cvtColor( img, gray, COLOR_BGR2GRAY );
    double fx = 1 / scale;
    resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR );
    equalizeHist( smallImg, smallImg );
    cascade.detectMultiScale( smallImg, faces,
        1.1, 2, 0
        //|CASCADE_FIND_BIGGEST_OBJECT
        //|CASCADE_DO_ROUGH_SEARCH
        |CASCADE_SCALE_IMAGE,
        Size(30, 30) );
    if( tryflip )
    {
        flip(smallImg, smallImg, 1);
        cascade.detectMultiScale( smallImg, faces2,
                  1.5, 2, 0
                  //|CASCADE_FIND_BIGGEST_OBJECT
                  //|CASCADE_DO_ROUGH_SEARCH
                  |CASCADE_SCALE_IMAGE,
                  Size(30, 30) );
        for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r )
        {
            faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
        }
    }
    for ( size_t i = 0; i < faces.size(); i++ )
    {
        Rect r = faces[i];
        Mat smallImgROI;
        vector<Rect> nestedObjects;
        Point center;
        Scalar color = colors[i%8];
        int radius;
int face[] = {cv::FONT_HERSHEY_SIMPLEX, cv::FONT_HERSHEY_PLAIN, cv::FONT_HERSHEY_DUPLEX, cv::FONT_HERSHEY_COMPLEX, 
              cv::FONT_HERSHEY_TRIPLEX, cv::FONT_HERSHEY_COMPLEX_SMALL, cv::FONT_HERSHEY_SCRIPT_SIMPLEX, 
              cv::FONT_HERSHEY_SCRIPT_COMPLEX, cv::FONT_ITALIC};
        double aspect_ratio = (double)r.width/r.height;
        if( 0.75 < aspect_ratio && aspect_ratio < 1.3 )
        {
      center.x = cvRound((r.x + r.width*0.5)*scale);
      center.y = cvRound((r.y + r.height*0.5)*scale);
      radius = cvRound((r.width + r.height)*0.25*scale);
      circle( img, center, radius, color, 3, 8, 0 );
        }
        else
    rectangle( img, cvPoint(cvRound(r.x*scale), cvRound(r.y*scale)),
    cvPoint(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)),color, 3, 8, 0);
        const int half_height=cvRound((float)r.height/2);
        r.y=r.y + half_height;
        r.height = half_height-1;
        smallImgROI = smallImg( r );
        nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
            1.1, 0, 0
            //|CASCADE_FIND_BIGGEST_OBJECT
            //|CASCADE_DO_ROUGH_SEARCH
            //|CASCADE_DO_CANNY_PRUNING
            |CASCADE_SCALE_IMAGE,
            Size(30, 30) );
        // The number of detected neighbors depends on image size (and also illumination, etc.). The
        // following steps use a floating minimum and maximum of neighbors. Intensity thus estimated will be
        //accurate only after a first smile has been displayed by the user.
        const int smile_neighbors = (int)nestedObjects.size();
        static int max_neighbors=-1;
        static int min_neighbors=-1;
        if (min_neighbors == -1)
        min_neighbors = smile_neighbors;
        max_neighbors = MAX(max_neighbors, smile_neighbors);
        // Draw rectangle on the left side of the image reflecting smile intensity
        float intensityZeroOne = ((float)smile_neighbors - min_neighbors) / (max_neighbors - min_neighbors + 1);
    //(float)img.rows
    double rect_height = cvRound((float)img.rows* intensityZeroOne)/2;
    char rectchar[256];
        Scalar col = Scalar((float)255 * intensityZeroOne, 0, 0);
    sprintf(rectchar, "%f", rect_height);
    //greenline
    line(img,Point(0,240), Point(100,240), Scalar(0,200,0), 5, CV_AA);
    if(rect_height>0)
      //positive line
      rectangle(img, cvPoint(0, 235), cvPoint(img.cols/10, 240-rect_height), CV_RGB(200,0,0), CV_FILLED);
    else
      //negative line
      rectangle(img, cvPoint(0,245), cvPoint(img.cols/10,480+rect_height*50), CV_RGB(0,0,200), CV_FILLED);
    //Text
    rectangle(img, cvPoint(400,430),cvPoint(630,395),CV_RGB(0,0,200), 3, 4);
    putText(img, rectchar, cv::Point(400,425), face[0], 1.2, cv::Scalar(0,0,200), 2, CV_AA);
    }
    imshow( "result", img );
}

試したこと

コマンド「lsusb」を打って、webカメラが認識されていることは分かっています。

補足情報(言語/FW/ツール等のバージョンなど)

Webカメラを使っての、リアルタイム表示は出来ています。
言語:c++
環境:Windows8
仮想空間:VMware Player
ツール:OpenCV3.3.1
参考サイト:https://so-zou.jp/software/tech/library/opencv/camera/ https://github.com/opencv/opencv/blob/master/samples/cpp/smiledetect.cpp

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • LouiS0616

    2017/11/03 15:37

    C++自体のコーディング経験、OpenCVの利用経験はどれくらいあるでしょうか?

    キャンセル

  • kotetu

    2017/11/03 15:37

    IpImageが含まれているwhileのところをコメントアウトさせました。

    キャンセル

  • kotetu

    2017/11/03 15:39

    C++自体のコーディング経験は、少しだけあります。OpenCVは、最近研究で使い始めました。

    キャンセル

回答 2

checkベストアンサー

+1

原因

ご提示のサンプルコードを動かしてみました。
確かに質問者様と同じような状態になりましたが、これは仕様なようです。

// Draw rectangle on the left side of the image reflecting smile intensity
float intensityZeroOne = ((float)smile_neighbors - min_neighbors) / (max_neighbors - min_neighbors + 1);
int rect_height = cvRound((float)img.rows * intensityZeroOne);
Scalar col = Scalar((float)255 * intensityZeroOne, 0, 0);
rectangle(img, cvPoint(0, img.rows), cvPoint(img.cols/10, img.rows - rect_height), col, -1);

『笑顔強度』を計算して、左側にバーで示しているのだとか。
試しにcaptureをWebカメラに接続して、いろんな表情をしてみてください。
実際にバーが上下すること、色合いが変わることが観測できます(顔面攣りかけました)。

サンプル

サンプルコードにはその他突っ込みたい点がいくつかありました。
初心者の方がこれを見て理解するのはちょっとしんどすぎると思います。

ですので、適当にコードを書いてみました。

#include <iostream>
#include <array>
#include <vector>

#include <opencv2/opencv.hpp>

const static std::array<cv::Scalar, 8> colors = {
    //          B    G    R
    cv::Scalar(255,   0,   0),  // Blue
    cv::Scalar(255, 128,   0),
    cv::Scalar(255, 255,   0),  // Cyan
    cv::Scalar(  0, 255,   0),  // Green
    cv::Scalar(  0, 128, 255),
    cv::Scalar(  0, 255, 255),  // Yellow
    cv::Scalar(  0,   0, 255),  // Red
    cv::Scalar(255,   0, 255)   // Magenta
};

void detectFacesAndSmiles(
    const cv::Mat &grayImg, cv::Mat &dst,
    cv::CascadeClassifier& faceCascade,
    cv::CascadeClassifier& smileCascade
);
void detectAndDraw(
    const cv::Mat& grayImg, cv::Mat &dst,
    cv::CascadeClassifier& cascade,
    std::vector<cv::Rect>& detected = std::vector<cv::Rect>()
);

int main(void) 
{
    // Load cascades
    cv::CascadeClassifier faceCascade, smileCascade;
    std::string faceCascadeName = "haarcascade_frontalface_alt.xml";
    if(!faceCascade.load(faceCascadeName)) {
        std::cerr << "ERROR: Could not load face cascade" << std::endl;
        return -1;
    }
    std::string smileCascadeName = "haarcascade_smile.xml";
    if(!smileCascade.load(smileCascadeName)) {
        std::cerr << "ERROR: Could not load smile cascade" << std::endl;
        return -1;
    }

    // Open web camera
    cv::VideoCapture capture(0);
    if(!capture.isOpened()) {
        std::cerr << "ERROR: Could not initiate capture" << std::endl;
        return -1;
    }

    // Main Process
    std::cout << "Video capturing has been started ..." << std::endl;

    cv::Mat frame, dst, gray;
    while(true) {
        capture >> frame;
        detectFacesAndSmiles(frame, dst, faceCascade, smileCascade);

        cv::imshow("Result", dst);
        char c = cv::waitKey(10);
        if(c == 27 || c == 'q' || c == 'Q') {
            break;
        }
    }

    return 0;
}

void detectFacesAndSmiles(
    const cv::Mat &img, cv::Mat &dst,
    cv::CascadeClassifier& faceCascade,
    cv::CascadeClassifier& smileCascade)
{
    // Prepare images
    cv::Mat gray;

    dst = img.clone();
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

    // Detect faces
    std::vector<cv::Rect> detectedFaces;
    detectAndDraw(
        gray, dst, faceCascade, detectedFaces
    );

    // Detect Smiles
    int colorNum = 0;
    for(const auto& face: detectedFaces) {
        cv::Scalar color = colors[colorNum++ % colors.size()];

        detectAndDraw(
            gray(face), dst(face), smileCascade
        );
    }
}

void detectAndDraw(
    const cv::Mat& grayImg, cv::Mat& dst,
    cv::CascadeClassifier& cascade,
    std::vector<cv::Rect>& detected)
{
    // Detect
    cascade.detectMultiScale(
        grayImg,            // image
        detected,           // dst objects
        1.1,                // scale factor
        2,                  // min neighbors
        0,                  // flag
        cv::Size(40, 40)    // min size
    );

    // Draw rectangle
    int colorNum = 0;
    for(auto face: detected) {
        cv::rectangle(
            dst,  
            cv::Point(face.x, face.y),                              
            cv::Point(face.x + face.width, face.y + face.height),
                        // Vertex of the rectangle
            colors[colorNum++ % colors.size()],
            3,          // thickness
            cv::LINE_8  // line type
        );

    }
}

本当はコードを分割すべきなのですが、そうすると前方宣言がややこしいので避けました。
一応これで、顔の矩形と笑顔領域が取得できます。

謎仕様について

笑顔の領域が乱立しますが、これも仕様であるようです。
おそらく当該カスケードは次のように解釈した方がよさそうです。

  • × 笑顔を検出するカスケードファイル
  •  笑顔であると判断できるパーツのカスケードファイル

(未確認ですが、サンプルコードを見る限りそうとしか思えない)
ですので、笑顔検出がしたいのなら、こいつの検出個数を数えた方が良いみたいです。

追記

yohhoyさんがコメント欄で貼られているリンクをここにも置いておきます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/03 18:02

    ありがとうございます!コードをコンパイルして、動かしてみますね。

    キャンセル

  • 2017/11/03 18:05 編集

    もう一度申し上げておきますと、このコードは『顔領域とその内部のわちゃわちゃ』を矩形で囲みます。
    ですので、純粋な『笑顔検出器』としては機能していないことに注意してください。
    また、検出時のパラメータは特に調整していないので精度は低いです。

    キャンセル

  • 2017/11/03 19:46

    LouiS0616さんも指摘する通り、OpenCVの笑顔検出器は(おそらく)kotetuさんが期待する様な魔法の万能アルゴリズムでは全くありません。実際に行われているのは「カスケード分類器」というアルゴリズムに、「人の笑顔らしい微小パーツの特徴カタログ」を入力しているだけです。非常に高速に動作するという反面、人間の目には全く関係ない領域も多数HITするのは、このアルゴリズムと特徴カタログの性能そのものです。

    http://opencv.jp/opencv-2.2/c/objdetect_cascade_classification.html
    https://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82

    より高精度な検出を行うには、他アルゴリズムや既知情報(人数や大きさなど)と組み合わせるか、機械学習技術などを用いた異なるアプローチが必要です。

    キャンセル

  • 2017/11/03 20:11

    OpenCV3.3からDNNも導入されましたし、やはり精度を追求するならNNですよね。
    GPUのスペックが充分あるなら速度もそれなりに思えます。

    キャンセル

  • 2017/11/03 20:11

    説明に有用なリンクのご提供ありがとうございます、
    コメント欄ではハイパーリンクが効かないので、回答に引用させていただきます。

    キャンセル

  • 2017/11/06 15:44

    コードをコンパイルしてみた所、';'がない事や、auto:faceのfaceがないと吐きました。

    キャンセル

  • 2017/11/06 15:48

    For-each文を使うには、コンパイラがC++11に対応している必要があります。

    キャンセル

0

知り合いからデュアルブートでも同じ現象になるのかと提案されたので、デュアルブートをしてみました。

デュアルブートをしたUbuntuでは、問題なく顔検出や笑顔検出されるようになりました。

今回の私が学んだ事としては、デュアルブートができる環境ではデュアルブートをする事をお勧めします。また、ネットワーク関係で少し苦労したので、関係ないかもしれませんが一応書いておきます。

IPアドレスなどの設定の際には、(私の場合は、有線でした。)Windwsのネットワーク設定の情報をそのままUbuntuの方で設定するといいでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 91.01%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    byte配列のcv::Matへの画像変換

    byte配列に格納している画像データをMatに画像配列として保存します。この時、cv::imdecodeを使用しています。その後に画素値の変換をかけてcv::imencodeで再び

  • 解決済

    OpenCvSharp3で重心を求めたいです

    以下のように、ラベリングを実行するプログラムはできたのですが、ここから重心の座標や面積を求めるプログラムを作成したいです。どなたか、教えていただけますか? 最終的な目標としては

  • 解決済

    OpenCVを用いた物体検出

    現在OpenCV2.1を用いた上で顔検出にチャレンジしています。 学習等については問題なく行うことができ、顔の検出を行おうと思っているのですが、検出の段階で設定するパラメータの"m

  • 解決済

    androidstudio 色検出

    androidstudioで色検出をしようとしています。このサイトのコードをほぼほぼコピペしたのですが FdActivity.javaでonCreateOptionsMenuの中の

  • 解決済

    OpenCVを使って動画の書き出しができない

    aviファイルを読み込み,1フレームずつ間隔をSleep関数を用いて変化させて再生速度を変えるプログラムを作っています. 0倍速,等倍速,2倍速,3倍速,4倍速,5倍速の速度を行き

  • 解決済

    android opencv で色検出

    私はandroidstudioでアプリ開発を行なっており今opencvを使っています。 フレームの中に入っている緑の色をしている領域の中で一番面積が大きいものを選び出し、その輪郭を

  • 解決済

    android opencvで輪郭描画

    私はandroidstudioでアプリ開発を行っています。 実現したいこと opencvで赤い色を検知して赤色を白に、それ以外を黒にしてそのあとに赤い物の輪郭の中心座標を返した

  • 解決済

    OPENCVにおけるエラー

    openCVをはじめようとして、以下のエラーがでました。 プログラミング初心者です。 visual studio community 2017を使っています。 opencv3.3.

同じタグがついた質問を見る

  • C++

    2936questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • Ubuntu

    1088questions

    Ubuntuは、Debian GNU/Linuxを基盤としたフリーのオペレーティングシステムです。

  • OpenCV

    830questions

    OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

  • VMware

    195questions

    VMwareとは、 ハードウェアで動作するOS上で仮想マシンを作成、実行するソフトウェアです。 Windows上でUNIX系OSを動作させたり、他のOS上で別の仮想OSを動作することが可能です。