Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Opencv: Edge detection, Dilation and Mass ceter drawing

My work is based on images with an array of dots (Fig. 1), and the final result is shown in Fig. 4. I will explain my work step by step.

Fig. 1 Original image

enter image description here

Step 1: Detect the edge of every object, including the dots and a "ring" that I want to delete for better performance. And the result of edge detection is shown in Fig.2. I used Canny edge detector but it didn't work well with some light-gray dots. My first question is how to close the contours of dots and reduce other noise as much as possible?

Fig. 2 Edge detection

enter image description here

Step 2: Dilate every object. I didn't find a good way to fill holes, so I dilate them directly. As shown in Fig.3, holes seem to be enlarged too much and so does other noise. My second question is how to fill or dilate the holes in order to make them be filled circles in the same/similar size?

Fig. 3 Dilation

enter image description here

Step 3: Find and draw the mass center of every dot. As shown in Fig. 4, due to the coarse image processing, there exist mark of the "ring" and some of dots are shown in two white pixels. The result wanted should only show the dots and one white pixel for one dot.

Fig. 4: Mass centers

enter image description here

Here is my code for these 3 steps. Can anyone help to make my work better?

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <cv.h>
#include <highgui.h>
using namespace std;
using namespace cv;

// Global variables
Mat src, edge, dilation;
int dilation_size = 2;

// Function header
void thresh_callback(int, void*);

int main(int argc, char* argv)
{
    IplImage* img = cvLoadImage("c:\\dot1.bmp", 0);         // dot1.bmp = Fig. 1

    // Perform canny edge detection
    cvCanny(img, img, 33, 100, 3);

    // IplImage to Mat
    Mat imgMat(img);
    src = img;

    namedWindow("Step 1: Edge", CV_WINDOW_AUTOSIZE);
    imshow("Step 1: Edge", src);

    // Apply the dilation operation
    Mat element = getStructuringElement(2, Size(2 * dilation_size + 1, 2 * dilation_size + 1), 
                  Point(dilation_size, dilation_size));     // dilation_type = MORPH_ELLIPSE
    dilate(src, dilation, element);
    // imwrite("c:\\dot1_dilate.bmp", dilation);            

    namedWindow("Step 2: Dilation", CV_WINDOW_AUTOSIZE);
    imshow("Step 2: Dilation", dilation);

    thresh_callback( 0, 0 );

    waitKey(0);
    return 0;
}

/* function thresh_callback */
void thresh_callback(int, void*)
{
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    // Find contours
    findContours(dilation, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

    // Get the moments
    vector<Moments> mu(contours.size());
    for(int i = 0; i < contours.size(); i++) {
        mu[i] = moments(contours[i], false);
    }

    // Get the mass centers
    vector<Point2f> mc(contours.size());
    for(int i = 0; i < contours.size(); i++) {
        mc[i] = Point2f(mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00); 
    }

    // Draw mass centers
    Mat drawing = Mat::zeros(dilation.size(), CV_8UC1);
    for( int i = 0; i< contours.size(); i++ ) {
        Scalar color = Scalar(255, 255, 255);
        line(drawing, mc[i], mc[i], color, 1, 8, 0);
    }

    namedWindow("Step 3: Mass Centers", CV_WINDOW_AUTOSIZE);
    imshow("Step 3: Mass Centers", drawing);
}
like image 790
WangYudong Avatar asked Jul 19 '13 13:07

WangYudong


2 Answers

There are a few things you can do to improve your results. To reduce noise in the image, you can apply a median blur before applying the Canny operator. This is a common de-noising technique. Also, try to avoid using the C API and IplImage.

    cv::Mat img = cv::imread("c:\\dot1.bmp", 0);         // dot1.bmp = Fig. 1

    cv::medianBlur(img, img, 7);

    // Perform canny edge detection
    cv::Canny(img, img, 33, 100);

This significantly reduces the amount of noise in your edge image: Canny result

To better retain the original sizes of your dots, you can perform a few iterations of morphological closing with a smaller kernel rather than dilation. This will also reduce joining of the dots with the circle:

// This replaces the call to dilate()
cv::morphologyEx(src, dilation, MORPH_CLOSE, cv::noArray(),cv::Point(-1,-1),2);

This will perform two iterations with a 3x3 kernel, indicated by using cv::noArray().

The result is cleaner, and the dots are completely filled:

Closing result

Leaving the rest of your pipeline unmodified gives the final result. There are still a few spurious mass centers from the circle, but considerably fewer than the original method:

Mass centers

If you wanted to attempt removing the circle from the results entirely, you could try using cv::HoughCircles() and adjusting the parameters until you get a good result. This might have some difficulties because the entire circle is not visible in the image, only segments, but I recommend you experiment with it. If you did detect the innermost circle, you could use it as a mask to filter out external mass centers.

like image 99
Aurelius Avatar answered Sep 21 '22 23:09

Aurelius


how to close contours of dots? use drawContours method with filled drawing option (CV_FILLED or thickness = -1)

reduce noise? use one of the blurring (low pass filtering) methods.

similar size? use erosion after dilation = morphological closing.

one dot for one circle, output without outer ring? find average of all contour areas. erase contours having big difference to this value. output the remaining centers.

Aurelius already mentioned most of these, but since this problem is quiet interesting, I will probably try and post a complete solution when I have enough time. Good luck.

like image 41
baci Avatar answered Sep 17 '22 23:09

baci