简体   繁体   中英

How to avoid detecting image frame when using findContours

How can I avoid detecting the frame of an image when using findContours (OpenCV)? Until I found OpenCV findContours allways finds two contours for every object and implemented that answer, I was not detecting the internal object consistently (object line was broken into several pieces), but now I detect the image frame every time.

The image is of a quad-rotor UAV seen from the bottom; I am using a series of pictures for 'training' object detection. For that, I need to be sure that I can consistently get the UAV object. I guess I could invert the colors, but that seems like a dirty hack.

The images are first the input image just before findContours , and the resulting contours. I have seven test images, and all seven has a frame and the UAV. The hu moments are very similar (as expected).

侵蚀后的二进制图像

二进制图像,检测到轮廓

The code (C++11, and quite messy) for finding the contours/objects and calculating the hu moments:

#include <opencv/cv.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <fstream>
#include <string>

using namespace cv;
using namespace std;

#define EROSION_SIZE 1
#define ERODE_CANNY_PREP_ITERATIONS 5

int main() {
    Mat image, canny_output, element, padded;
    RNG rng(12345);
    int numbers[] = {195, 223, 260, 295, 331, 368, 396};
    string pre = "/home/alrekr/Pictures/UAS/hu-images/frame_";
    string middle = "_threshold";
    string post = ".png";
    string filename = "";
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    ofstream fout("/home/alrekr/Pictures/UAS/hu-data/hu.dat");
    element = getStructuringElement(MORPH_RECT,
            Size(2*EROSION_SIZE + 1, 2*EROSION_SIZE+1),
            Point(EROSION_SIZE, EROSION_SIZE));
    namedWindow("Window", CV_WINDOW_AUTOSIZE);
    for (int i : numbers) {
        filename = pre + to_string(i) + middle + post;
        image = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
        erode(image, image, element, Point(-1,-1), ERODE_CANNY_PREP_ITERATIONS);
        imwrite("/home/alrekr/Pictures/UAS/hu-data/prep_for_canny_" + to_string(i) + ".png", image);
    findContours(image, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
        vector<Moments> mu(contours.size());
        if(contours.size() < 1) {
            cout << "No contours found" << endl;
        } else {
            cout << "Contours found: " << contours.size() << endl;
        }
        vector<Point2f> mc(contours.size());
        for(int j = 0; j < (int)contours.size(); j++) {
            mc[j] = Point2f(mu[j].m10/mu[j].m00 , mu[j].m01/mu[j].m00);
        }
        Mat drawing = Mat::zeros(image.size(), CV_8UC3);
        for(int j = 0; j < (int)contours.size(); j++) {
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255));
            drawContours(drawing, contours, j, color, 2, 8, hierarchy, 0, Point());
            imshow("Window", drawing);
            waitKey(0);
        }
        imwrite("/home/alrekr/Pictures/UAS/hu-data/cannied_" + to_string(i) + ".png", drawing);
        fout << "Frame " << i << "\n";
        for(int j = 0; j < (int)contours.size(); j++) {
            mu[j] = moments(contours[j]);
            double hu[7];
            HuMoments(mu[j], hu);
            fout << "Object " << to_string(j) << "\n";
            fout << hu[0] << "\n";
            fout << hu[1] << "\n";
            fout << hu[2] << "\n";
            fout << hu[3] << "\n";
            fout << hu[4] << "\n";
            fout << hu[5] << "\n";
            fout << hu[6] << "\n";
        }
    }
    fout.close();
    return 0;
}

The function cv::findContours describes the contour of areas consisting of ones. The areas in which you are interested are black, though.

So the solution is simple. Invert the input image before detecting contours:

image = 255 - image;

Below is a code example which I derived from your example above:

#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>
#include <string>

#define EROSION_SIZE 1
#define ERODE_CANNY_PREP_ITERATIONS 5

int main( int argc, char ** argv )
{
    // Display the version of the linked OpenCV library.
    std::cout << "Using OpenCV " << CV_VERSION_MAJOR << "." << CV_VERSION_MINOR << ".";
    std::cout << CV_VERSION_REVISION << CV_VERSION_STATUS << std::endl;

    // Load the input file.
    std::string filename = std::string( argv[ 1 ] );
    cv::Mat image = imread( filename, cv::IMREAD_GRAYSCALE );

    // Invert the image so the area of the UAV is filled with 1's. This is necessary since
    // cv::findContours describes the boundary of areas consisting of 1's.
    image = 255 - image;

    // Detect contours.
    std::vector< std::vector< cv::Point> > contours;
    std::vector< cv::Vec4i > hierarchy;

    cv::findContours( image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
    std::cout << "Contours found: " << contours.size() << std::endl;

    // Display and save the results.
    cv::RNG rng( 12345 );
    cv::Mat contourImage = cv::Mat::zeros( image.size(), CV_8UC3);

    for( size_t j = 0; j <  contours.size(); j++ )
    {
        cv::Scalar color( rng.uniform( 0, 255 ), rng.uniform( 0,255 ), rng.uniform( 0, 255 ) );
        cv::drawContours( contourImage, contours, j, color, 2, 8, hierarchy, 0, cv::Point() );
    }

//  cv::imwrite( "contours.png", contourImage );

    cv::imshow( "contours", contourImage );
    cv::waitKey( 0 );

    return 0;
}

The console output is as follows:

$ ./a.out gvlGK.png 
Using OpenCV 3.0.0-beta
Contours found: 1

and the resulting contour image is this:

等高线

Another solution would be :-

find the bounding Rectangle of the contour

x,y,w,h = cv2.boundingRect(c)

compare the size of the image with the size of the bounding Rectangle for example

cnt_size=w*h
if(abs(cnt_size-img_size<=ERROR_THRESHOLD):
    ##discard this contour 

If you have white background, first inverse it using THRESH_BINARY_INV type and then use contour.

    image = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
    threshold(image,image,100,255,THRESH_BINARY_INV);
    findContours( image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );

This will only return the contour that you need.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM