c++opencvedge-detectionobject-detectioncanny-operator

OpenCV - find object shape


I'm learning OpenCV and I've reached a point where no matter what I do, I get stuck. What I'm trying to do is to isolate an object (rectangular object) from its background.

An example is the following picture of a battery: battery-picture

I want to mask that image so that the only thing that remains is the object.

I've tried the following:

But I'm getting some strange area as the bigger one. Here are the resulting pictures:

Here's the code I'm using:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;
using namespace std;


int main( int, char** argv )
{
Mat src, srcGray,srcBlur,srcCanny;

string file = "samsung";
src = imread(file + ".jpg");
cvtColor(src, srcGray, CV_BGR2GRAY);
//bilateralFilter(srcGray, srcBlur,11, 17, 17);
srcBlur = srcGray.clone();
imshow("Filtered", srcBlur);
imwrite(file+"-filtered.jpg",srcBlur);

Canny(srcBlur, srcCanny, 0, 100, 3, true);
imshow("Canny", srcCanny);
imwrite(file+"-canny.jpg",srcCanny);


vector< vector <Point> > contours; // Vector for storing contour
vector< Vec4i > hierarchy;
findContours( srcCanny.clone(), contours, hierarchy,CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE ); // Find the contours in the image


int largest_contour_index=0;
int largest_area=0;
for( int i = 0; i< contours.size(); i++ ){
    double a=contourArea( contours[i],false);  //  Find the area of contour
    if(a>largest_area){
    largest_area=a;
    largest_contour_index=i;                //Store the index of largest contour
    }
 }


Mat dst(src.rows,src.cols,CV_8UC1,Scalar::all(0)); //create destination image
drawContours( dst,contours, largest_contour_index, Scalar(255,0,0),CV_FILLED, 8, hierarchy );
imshow("Largest", dst);
imwrite(file+"-largest.jpg",dst);

waitKey();
}

This piece of code was intended to get the 'mask' of the object then masking should be applied but I can't move forward because I can't detect the object

My goal is to detect rectangular objects (only one object per image) in different images.

The idea was taken from here, but I can't manage to get that code to work with a lesser contrast image like mine.

I've also try this which is pretty much the same as I want.

I want to isolate a rectangular object (which should be the bigger one in the image)

Thanks in advance!

PS: Although I can translate Python to C++ I would appreciate the answer directly in C++ so I can test it faster.


Solution

  • This is what I hacked together, sorry, it's in Python :)

    First, resize the image to 1/4 of the original size (probably going to work without resizing, although with different parameters) and apply median blur:

    w, h, c = img_in.shape #img_in is the input image
    resize_coeff = 0.25
    img = cv2.resize(img_in, (int(resize_coeff*h), int(resize_coeff*w)))
    img = cv2.medianBlur(img, 15)
    

    What's good about median blur is that it removes most of the noise and tiny unnecessary details like those blue marker lines, while keeping the edges of larger shapes non-blurred. Now, let's apply Canny edge detection:

    img = cv2.Canny(img, 100, 200)
    

    enter image description here

    Unfortunately, there are some tiny gaps in our edges, but that can be fixed with dilate/erode:

    kernel = np.ones((17, 17), np.uint8)
    img = cv2.dilate(img, kernel, 1)
    img = cv2.erode(img, kernel, 1)
    

    enter image description here

    Now we can find our contours, take the largest one by area, and it's probably going to be what we want:

    img, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    max_index, max_area = max(enumerate([cv2.contourArea(x) for x in contours]), key = lambda x: x[1])
    max_contour = contours[max_index]
    

    Drawing it on top of the original (scaled) image, we get this:

    img_out = cv2.resize(img_in, (int(resize_coeff*h), int(resize_coeff*w)))
    cv2.drawContours(img_out, [max_contour], 0, (0, 0, 255), 2)
    

    enter image description here

    By some simple contour smoothing we can easily get rid of the wires on top, if we want to. Don't know what to do with the shadow at the bottom, though.