How to identify whether or not the area of an object is within another area desired by the user, drawn by mouse

1

Hello! I recently started to venture into the OpenCV library in C ++. Today, I want to create a program that applies a classifier in a polygon ROI (5 points), obtained through 5 clicks with the mouse on the cam image. The classifier can only stay within this ROI, not out.

Thanks to several topics here, I was able to put together some examples and put the code below that does what I need but in a rectangular area. The "Onmouse" function draws the rectangle. However, I can not get past this when trying to use other topics that mention ROI with polygon. Could anyone help with suggestion of code =).

I want to get a polygon ROI with a mouse instead of a rectangle.

Thanks for the help! =)

Follow the code.

#include "opencv2/highgui/highgui.hpp"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include "opencv2/core/core.hpp"
#include "opencv2/flann/miniflann.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include "opencv2/video/video.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/ml/ml.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core_c.h"
#include "opencv2/highgui/highgui_c.h"
#include "opencv2/imgproc/imgproc_c.h"

using namespace std;
using namespace cv;


Mat src,img,ROI;
Rect cropRect(0,0,0,0);
 Point P1(0,0);
 Point P2(0,0);

const char* winName="Imagem de Origem";
bool clicked=false;
int i=0;
char imgName[15];

const char* WinComClassificador="Imagem COM CLASSIFICADOR";
const char* WinSemClassificador="Imagem SEM CLASSIFICADOR";

void detectAndDisplay( Mat frame);

String face_cascade_name =     "C:/Code/Projects_Tests/Cascade_Classifier_not_displaying/Cascade_Classifier/haarcascade_frontalface_alt.xml";
String eyes_cascade_name = "C:/Code/Projects_Tests/Cascade_Classifier_not_displaying/Cascade_Classifier/haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;


void detectAndDisplay( Mat frame )
{
    std::vector<Rect> faces;
    Mat frame_gray;

    cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
    equalizeHist( frame_gray, frame_gray );

    //-- Detect faces
    face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2,     0|CASCADE_SCALE_IMAGE, Size(30, 30) );

    for ( size_t i = 0; i < faces.size(); i++ )
    {
        Point center( faces[i].x + faces[i].width/2, faces[i].y +     faces[i].height/2 );
        ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ),     0, 0, 360, Scalar( 255, 0, 0 ), 4, 8, 0 );

        Mat faceROI = frame_gray( faces[i] );
        std::vector<Rect> eyes;

        //-- In each face, detect eyes
        eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0     |CASCADE_SCALE_IMAGE, Size(30, 30) );

        for ( size_t j = 0; j < eyes.size(); j++ )
        {
            Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );
            int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
            circle( frame, eye_center, radius, Scalar( 0, 255, 255), 4, 8, 0     );
        }
    }



    //-- Show what you got
    //imshow( window_name, frame );
}

void checkBoundary(){
       if(cropRect.width>img.cols-cropRect.x)
         cropRect.width=img.cols-cropRect.x;

       if(cropRect.height>img.rows-cropRect.y)
         cropRect.height=img.rows-cropRect.y;

        if(cropRect.x<0)
         cropRect.x=0;

       if(cropRect.y<0)
         cropRect.height=0;
}

void showImage(){
    img=src.clone();
    checkBoundary();
    if(cropRect.width>0&&cropRect.height>0){
        ROI=src(cropRect);
         imshow("Regiao de Interesse",ROI);
    }
    rectangle(img, cropRect, Scalar(0,255,0), 1, 8, 0 );
    imshow(winName,img);
}


void onMouse( int event, int x, int y, int f, void* ){

    switch(event){

        case  CV_EVENT_LBUTTONDOWN  :
                                        clicked=true;

                                        P1.x=x;
                                        P1.y=y;
                                        P2.x=x;
                                        P2.y=y;
                                        break;

        case  CV_EVENT_LBUTTONUP    :
                                        P2.x=x;
                                        P2.y=y;
                                        clicked=false;
                                        break;

        case  CV_EVENT_MOUSEMOVE    :
                                        if(clicked){
                                        P2.x=x;
                                        P2.y=y;
                                        }
                                        break;

        default                     :   break;

    }


    if(clicked){
     if(P1.x>P2.x){ cropRect.x=P2.x;
                       cropRect.width=P1.x-P2.x; }
        else {         cropRect.x=P1.x;
                       cropRect.width=P2.x-P1.x; }

        if(P1.y>P2.y){ cropRect.y=P2.y;
                       cropRect.height=P1.y-P2.y; }
        else {         cropRect.y=P1.y;
                       cropRect.height=P2.y-P1.y; }

    }

showImage();

}
int main()
{
    string msg("OI FELIPE");
    VideoCapture cap(0);
    cap.open( 0 );
    cap >> src;
    if ( ! cap.isOpened() ) { printf("--(!)Error opening video capture\n");     return -4; }

    cout << "Recording..." << endl;
    namedWindow(winName,WINDOW_NORMAL);

        //-- 1. Load the cascades
    if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error     loading face cascade\n"); return -1; };
    if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error     loading eyes cascade\n"); return -1; };


    while (cap.read(src)) {  

   // imshow( "MyVideo", src );

    if (!clicked) {
        imshow(winName,src);
        setMouseCallback(winName,onMouse,NULL );

        if( src.empty() )
                break;

    }
   else {
        imshow(winName,src);
        setMouseCallback(winName,onMouse,NULL );

        if( src.empty() )
                break;
        }

    showImage();

        int c=waitKey(10);   

    }

    waitKey(10);
    return 0;
    }
    
asked by anonymous 13.02.2017 / 00:44

1 answer

4
  

After much discussion I finally understood that what you wanted   to do (and it was not on your merit, by the way;   In the future, try to focus on the problem instead of focusing on   solution that finds that works).

What you want is not to run Cascade within a limited area (even because that does not make any sense!). What you want to know is whether or not the area of an object as detected by Cascade (in this case, the region where a human face is located) is within another area marked by the user.

Since the Cascade area is an ROI (which is rectangular in principle, since it is a sub-area of an image) and its check area is a polygon with more sides, this comparison is not trivial. Because both polygons are closed, you could do a simple "completely in or out" check simply by checking whether or not all of the vertices in the ROI are within the polygon's boundaries. It could also do a "collision" check, just as the games do (and have a series of techniques for this ). However, you do not need to know whether one area "touched" the other, but whether one area contains the other. Therefore, using collision techniques would potentially produce many false positives.

As you use OpenCV, a very simple and effective way is to do so:

  • Draw each area in a separate binary image (that is, in an image that has only black and white), filling the area of the region completely blank.
  • Make a logical "E" of the two images, pixel by pixel. Pixels all in black are 0 , and all pixels in white are worth 1 . Since 0 E 0 or 0 E 1 result in 0 , only pixels that are 1 (white) in both images will be retained in the resulting image.
  • Count the blank (non-zero) pixels in the resulting image, and divide this value by the number of pixels in the area originally detected. This is a percentage that therefore ranges from 0 to 1. A detected ROI that is fully within your polygon will result in 1 (ie, 100% inside!) And an ROI that is totally outside your polygon will result in 0 (i.e., 100% off).
  • With this value, create some decision criterion to consider as "invasion" or not (for example, consider as invaded only when the given value is greater than 0.7, or 70%). >

    The code

    #include <opencv2\core.hpp>
    #include <opencv2\highgui.hpp>
    #include <opencv2\imgproc.hpp>
    
    #include <vector>
    #include <iostream>
    
    using namespace std;
    using namespace cv;
    
    Size sz = Size(800, 600);
    Mat image = Mat::zeros(sz, CV_8UC3);
    Rect roi;
    vector<Point> polygon;
    bool buttonDown = false;
    
    typedef enum
    {
        CAPTURING_POLYGON,
        CAPTURING_ROI
    } Capturing;
    Capturing cap;
    
    void roiCapture(int event, int x, int y, int flags, void *userdata)
    {
        if(event == EVENT_LBUTTONDOWN)
        {
            roi = Rect(x, y, 0, 0);
            buttonDown = true;
        }
        else if(event == EVENT_LBUTTONUP)
        {
            buttonDown = false;
            roi = Rect(Point(roi.x, roi.y), Point(x, y));
        }
        else if(buttonDown)
            roi = Rect(Point(roi.x, roi.y), Point(x, y));       
    }
    
    void polygonCapture(int event, int x, int y, int flags, void *userdata)
    {
        if(event == EVENT_LBUTTONUP)
            polygon.push_back(Point(x, y));
        else if(event == EVENT_RBUTTONDOWN)
            polygon.clear();
    }
    
    float calcOverlapping(const Rect &roi, const vector<Point> &polygon, const Size &sz, Mat &resp)
    {
        // Desenha o polígono preenchido com branco em uma imagem binária toda em preto
        Mat imgPol = Mat::zeros(sz, CV_8UC1);
        vector<vector<Point>> points;
        points.push_back(polygon);
        int npts = polygon.size();
        fillPoly(imgPol, points, Scalar(255));
    
        // Desenha o retângulo preenchido com branco em uma imagem binária toda em preto
        Mat imgRoi = Mat::zeros(sz, CV_8UC1);
        rectangle(imgRoi, roi, Scalar(255, 255, 255), CV_FILLED);
    
        // Faz um "E" lógico das imagens. Isso causa uma interseção das áreas, pois apenas os pixels
        // que forem branco NAS DUAS imagens irão permanecer em branco na imagem de resposta.
        bitwise_and(imgPol, imgRoi, resp);
    
        // Conta os pixels que são diferentes de preto (0) na imagem resultante
        int intersec = countNonZero(resp);
    
        // Conta os pixels que são diferentes de preto (0) na imagem da ROI (porque a área dela é
        // usada como base (isto é, que se saber qual é a porcentagem da ROI que está dentro do 
        // polígono)
        int base = countNonZero(imgRoi);
    
        // A resposta é um percentual (entre 0 e 1), indicando quanto da ROI está na interseção.
        // Se a ROI estiver inteira dentro do polígono, a interseção vai ser total e esse valor vai
        // dar 1. Se estiver totalmente fora, a interseção vai ser nula e esse valor vai dar 0.
        return float(intersec) / float(base);
    }
    
    int main()
    {
        // ===========================================
        // Captura das áreas para teste.
        // Essa parte você já faz (ou sabe fazer).
        // ===========================================
        namedWindow("Captura", WINDOW_AUTOSIZE);
    
        int fontFace = FONT_HERSHEY_SIMPLEX;
        double fontScale = 0.5;
        int thickness = 1;
    
        // Começa capturando a área do polígono
        cap = CAPTURING_POLYGON;
        setMouseCallback("Captura", polygonCapture, NULL);
        bool ok = true;
        while(ok)
        {
            // Pinta tudo de preto
            image = Scalar(0, 0, 0);
    
            // Desenha o texto de ajuda
            string text;
            if(cap == CAPTURING_ROI)
                text = "Desenhe o ROI com o mouse. Pressione [C] para continuar.";
            else
                text = "Adicione pontos ao POLIGONO com o mouse (botao direito limpa). Pressione [C] para continuar.";
            putText(image, text, Point(0, 15), fontFace, fontScale, Scalar(255, 255, 255), thickness);
    
            // Desenha o polígono, se ele tiver dados
            if(polygon.size() == 1)
                circle(image, polygon[0], 1, Scalar(255, 0, 255));
            else if(polygon.size() > 1)
            {
                vector<vector<Point>> points;
                points.push_back(polygon);
                int npts = polygon.size();
                fillPoly(image, points, Scalar(255, 0, 255));
            }
    
            // Desenha a ROI, se ela tiver dados
            if(roi.width != 0 || roi.height != 0)
                rectangle(image, roi, Scalar(0, 255, 255));
    
            imshow("Captura", image);
            int k = waitKey(10);
            switch(k)
            {
                case 27: // ESC
                case 'q':
                case 'Q':
                    destroyAllWindows();
                    return 0;
    
                case 'c':
                case 'C':
                    if(cap == CAPTURING_POLYGON)
                    {
                        cap = CAPTURING_ROI;
                        // Troca pra captura da ROI
                        setMouseCallback("Captura", roiCapture, NULL);
                    }
                    else
                        ok = false;
                    break;
            }
        }
    
        // ===========================================
        // Cálculo do percentual de interseção.
        // Essa parte é a novidade.
        // ===========================================
    
        Mat resp;
        float val = calcOverlapping(roi, polygon, sz, resp);
    
        cout << "Valor de intereseção calculado: " << val << endl;
    
        imshow("Debug", resp);
        waitKey(0);
    
        return 0;
    }
    

    The really important function is calcOverlapping , which does the "magic". :)

    Result Examples

    In the following examples, the left image indicates the ROI (in yellow) and the polygon (in magenta), and the image on the right indicates only the intersection area in the resulting binary image of the logical E between the two binary images constructed from the figures in the left image.

    Valordeinterseçãocalculado:0.748836

    Valordeinterseçãocalculado:0.012016

    Edition:RealUseExample

    Youhavecreated#

    Well,ifyouwanttheCascadedetectortofindonlypeoplewithintheregionboundedbytheredpolygon,justusewhatIexplainedaboveandmakethelogicaloperationtohaveonlythepixelsintheregionpolygonintoaROIthatcontinuestoberectangular(the"final" image in the example below). Then apply Cascade on this new image . Example (in Python this time, because I did it in a hurry - but it's the same thing in C ++):

    import numpy as np
    import cv2
    
    image = cv2.imread('teste.png', cv2.IMREAD_COLOR)
    
    # Define a Região de Interesse (ROI)
    left = 15
    top = 118
    width = 615
    height = 365
    roi = image[top:height, left:width]
    
    # Define o polígono delimitador
    points = np.array([[(0, 184), (165, 40), (402, 0), (598, 20), (482, 56), (342, 245)]])
    mask = np.zeros(roi.shape, roi.dtype)
    cv2.fillPoly(mask, points, (255, 255, 255)) # Preenche com branco (note que a cor é RGB - i.e. 3 bandas, já que a imagem não é binária!)
    
    final = cv2.bitwise_and(roi, mask)
    
    cv2.imshow('Imagem Original', image)
    cv2.imshow('ROI', roi)
    cv2.imshow('Mascara', mask)
    cv2.imshow('Final', final)
    
    cv2.waitKey(0)
    

    Result:

        
  • 15.02.2017 / 05:53