LoginSignup
13
8

More than 5 years have passed since last update.

紗霧にプレゼント「たぶん絶対とれないデジタルお面」

Last updated at Posted at 2017-06-29

hikaru3.gif

liveemt.PNG

liveemt2.PNG
実際にtwitcastingで配信したライブ映像から

⏰兄さんならこの記事の内容は約5分で実行できるぜっ

【注意】素人が書いた記事です。なまあたたかいめでみてね

仕様

  • 某キャラのお面をデジタルで再現した
    • デジタルなので、お面かぶったまま食べたり変顔したりできる
  • 血迷ってかわいい仮面がたまに笑い男に化けるように仕込んだのは内緒(ヒトリゴト)
  • 顔が見つからないときは画面全体にモザイクをかけるようにした(よしっ!)

わけあって急いで書きましたいつか書き直します。。。
(あんたってやつは……どこまで愚かなの!
そんなやり方をしているからあんたの書く小説プログラム)はつまらないのよ!

とりあえず張っていくスタイル

emt-mask/
 ├ facedetect.cpp
 ├ data/
 │ ├ haarcascade_eye_tree_eyeglasses.xml
 │ └ haarcascade_frontalface_alt.xml
 ├ image/
 │ ├ emt.png
 │ └ tlm.png

xmlファイルはgithub.com/opencv/にある
cppファイルは、OpenCVのサンプルをちょといじっただけ

facedetect.cpp
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

static void help()
{
    cout << "\nThis program demonstrates the cascade recognizer. Now you can use Haar or LBP features.\n"
            "This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.\n"
            "It's most known use is for faces.\n"
            "Usage:\n"
            "./facedetect [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]\n"
            "   [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]\n"
            "   [--scale=<image scale greater or equal to 1, try 1.3 for example>]\n"
            "   [--try-flip]\n"
            "   [filename|camera_index]\n\n"
            "see facedetect.cmd for one call:\n"
            "./facedetect --cascade=\"data/haarcascades/haarcascade_frontalface_alt.xml\" --nested-cascade=\"data/haarcascades/haarcascade_eye_tree_eyeglasses.xml\" --scale=1.3\n\n"
            "During execution:\n\tHit any key to quit.\n"
            "\tUsing OpenCV version " << 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;
    CascadeClassifier cascade, nestedCascade;
    double scale;


    /****************/
    /*
    cv::CommandLineParser parser(argc, argv,
        "{help h||}"
        "{cascade|../../data/haarcascades/haarcascade_frontalface_alt.xml|}"
        "{nested-cascade|../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|}"
        "{scale|1|}{try-flip||}{@filename||}");
    */
    cv::CommandLineParser parser(argc, argv,
        "{help h||}"
        "{cascade|data/haarcascade_frontalface_alt.xml|}"
        "{nested-cascade|data/haarcascade_eye_tree_eyeglasses.xml|}"
        "{scale|1|}{try-flip||}{@filename||}");
    /****************/
    if (parser.has("help")) {
        help();
        return 0;
    }
    cascadeName = parser.get<string>("cascade");
    nestedCascadeName = parser.get<string>("nested-cascade");
    scale = parser.get<double>("scale");
    if (scale < 1)
        scale = 1;
    tryflip = parser.has("try-flip");
    inputName = parser.get<string>("@filename");
    if (!parser.check()) {
        parser.printErrors();
        return 0;
    }
    if (!nestedCascade.load(nestedCascadeName))
        cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
    if (!cascade.load(cascadeName)) {
        cerr << "ERROR: Could not load classifier cascade" << endl;
        help();
        return -1;
    }
    if (inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1)) {
        int camera = inputName.empty() ? 0 : inputName[0] - '0';
        if (!capture.open(camera))
            cout << "Capture from camera #" << camera << " didn't work" << endl;
    } else if (inputName.size()) {
        image = imread(inputName, 1);
        if (image.empty()) {
            if (!capture.open(inputName))
                cout << "Could not read " << inputName << endl;
        }
    } else {
        image = imread("../data/lena.jpg", 1);
        if (image.empty())
            cout << "Couldn't read ../data/lena.jpg" << endl;
    }

    if (capture.isOpened()) {
        cout << "Video capturing has been started ..." << endl;

        for (;;) {
            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 {
        cout << "Detecting face(s) in " << inputName << endl;
        if (!image.empty()) {
            detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
            waitKey(0);
        } else if (!inputName.empty()) {
            /* assume it is a text file containing the
            list of the image filenames to be processed - one per line */
            FILE* f = fopen(inputName.c_str(), "rt");
            if (f) {
                char buf[1000 + 1];
                while (fgets(buf, 1000, f)) {
                    int len = (int)strlen(buf);
                    while (len > 0 && isspace(buf[len - 1]))
                        len--;
                    buf[len] = '\0';
                    cout << "file " << buf << endl;
                    image = imread(buf, 1);
                    if (!image.empty()) {
                        detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
                        char c = (char)waitKey(0);
                        if (c == 27 || c == 'q' || c == 'Q')
                            break;
                    } else {
                        cerr << "Aw snap, couldn't read image " << buf << endl;
                    }
                }
                fclose(f);
            }
        }
    }

    return 0;
}

void detectAndDraw(Mat& img, CascadeClassifier& cascade,
    CascadeClassifier& nestedCascade,
    double scale, bool tryflip)
{
    double t = 0;
    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);

    t = (double)getTickCount();
    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.1, 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));
        }
    }
    t = (double)getTickCount() - t;

    /****************/
    //printf("detection time = %g ms\n", t * 1000 / getTickFrequency());
    /****************/

    /****************/
    if (faces.size() == 0) {
        cv::GaussianBlur(img, img, cv::Size(63, 63), 15, 15);
    }
    /****************/

    for (size_t i = 0; i < faces.size(); i++) {

        /****************/
        Rect r2 = faces[i];
        Rect r;

        double emt_ratio = 1.2;
        r.x = r2.x - r2.width * ((emt_ratio - 1.0) / 2.0);
        r.y = r2.y - r2.height * ((emt_ratio - 1.0) / 2.0);
        r.width = r2.width * emt_ratio;
        r.height = r2.height * emt_ratio;
        /*
        double emt_ratio = 1.5;
        r.x = r2.x - r2.width / 4;
        r.y = r2.y - r2.height / 4;
        r.width = r2.width * emt_ratio;
        r.height = r2.height * emt_ratio;
        */
        //rectangle(img, Point(r.x * scale, r.y * scale), Point((r.x + r.width) * scale, (r.y + r.height) * scale), Scalar(0, 0, 0));
        /****************/

        /****************/
        Mat emt_img;
        if (rand() % 100 > 90) {
            emt_img = cv::imread("image/tlm.png", 1);
        } else {
            emt_img = cv::imread("image/emt.png", 1);
        }

        double ratio = (double)r.width / emt_img.cols;
        resize(emt_img, emt_img, Size(), ratio, ratio);
        for (int l = 0; l < emt_img.cols; l++) {
            for (int m = 0; m < emt_img.rows; m++) {
                int p = m * emt_img.cols * 3 + l * 3;
                int my = (m + r.y + r.height / 2 - emt_img.rows / 2);
                int mx = (l + r.x);
                int q = my * img.cols * 3 + mx * 3;
                if (0 <= mx && mx < img.cols && 0 < my && my < img.rows && emt_img.data[p + 0] * emt_img.data[p + 1] * emt_img.data[p + 2]) {
                    img.data[q + 0] = emt_img.data[p + 0];
                    img.data[q + 1] = emt_img.data[p + 1];
                    img.data[q + 2] = emt_img.data[p + 2];
                }
            }
        }
        /****************/

        /****************/
        /*
        Mat smallImgROI;
        vector<Rect> nestedObjects;
        Point center;
        Scalar color = colors[i % 8];
        int radius;
        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);
        */

        /*
        if (nestedCascade.empty())
            continue;
        smallImgROI = smallImg( r );
        nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
            1.1, 2, 0
            //|CASCADE_FIND_BIGGEST_OBJECT
            //|CASCADE_DO_ROUGH_SEARCH
            //|CASCADE_DO_CANNY_PRUNING
            |CASCADE_SCALE_IMAGE,
            Size(30, 30) );
        for ( size_t j = 0; j < nestedObjects.size(); j++ )
        {
            Rect nr = nestedObjects[j];
            center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale);
            center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale);
            radius = cvRound((nr.width + nr.height)*0.25*scale);
            circle( img, center, radius, color, 3, 8, 0 );
        }
        */
    }
    imshow("result", img);
}


emt.png
https://images-na.ssl-images-amazon.com/images/I/41GzDrldNbL.jpg


tlm.png
https://img.atwikiimg.com/www49.atwiki.jp/aniwotawiki/attach/34612/4435/%E7%AC%91%E3%81%84%E7%94%B7%E3%83%9E%E3%83%BC%E3%82%AF.jpg

コンパイルは以下のページを参考にalias書いた上で
OpenCV開発 コンパイルから動作確認まで
$ gcv facedetect.cpp

$ ./a.out

おわり

リポジトリ

GitHub

ヒトリゴト

読んでくださってありがとうございます

\def\textlarge#1{%
{\rm\Large #1}}
\def\textsmall#1{%
{\rm\scriptsize #1}}

(精一杯小声で)
  $\textsmall{い い ね く だ さ い}$

ヒトリゴトだよ(恥ずかしいこと 聞かないでよね :notes:

13
8
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
13
8