7

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);
}
WangYudong
  • 4,335
  • 4
  • 32
  • 54
  • Have you tried anything from [here](http://stackoverflow.com/questions/1716274/fill-the-holes-in-opencv) yet? – LovaBill Jul 19 '13 at 13:51

2 Answers2

10

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.

Aurelius
  • 11,111
  • 3
  • 52
  • 69
  • Thanks, you did improve my work. Another question: I noticed a little difference between cv::Canny and cvCanny. Why they created two similar functions and which one (fucntions with prefix `cv::` and `cv`) should I use more often? – WangYudong Jul 20 '13 at 03:19
  • There should be no difference between the two--they use the same implementation internally. `cv::Canny()` is used with `cv::Mat` in the C++ API, which you should prefer. `cvCanny()` is from the C API, which uses `IplImage`, and is deprecated. – Aurelius Jul 22 '13 at 15:36
5

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.

baci
  • 2,528
  • 15
  • 28