1

I'm writing a program in OpenCV which takes a picture of parasite eggs and attempts to identify at least a large portion of them. My problem is that the input image I have the best results with has a large background. I've tried both filling in the background, and cropping it out, but when I do I get a worse selection of the eggs.

My currently thought out solution is to use the image with the background and then fill it in. It felt like it would be easy because I just want to fill anything outside of that circle with black, but I'm not sure how to actually perform the action. If anyone could point be toward a method to use, or any suggestions that would be great.

Here is a link to what the image looks like:

enter image description here

Thanks!

karlphillip
  • 92,053
  • 36
  • 243
  • 426
ahansen
  • 234
  • 4
  • 15

2 Answers2

1

Worked out a fix for my problem, I created a mouse event callback which fills in whatever I click with black. Below is the code I used in the callback:

def paint(event, x, y, flags, param):
    global opening                                                                                                                         

    if event == cv2.EVENT_LBUTTONDOWN:
        h, w = opening.shape[:2]
        mask = np.zeros((h+2, w+2), np.uint8)
        cv2.floodFill(opening, mask, (x,y), (0, 0, 0)) 
        cv2.imshow("open", opening)
ahansen
  • 234
  • 4
  • 15
1

It seems that you need the exterior of the image to be filled with black because it makes it easier to identify the eggs since they will be isolated in white.

But what if the parasite eggs magically appeared as blue? I'll explain this in a second, but this approach would free you from the burden of clicking on the image every time a new sample needs to be analyzed.

I wrote the answer in C++, but you if follow what the code does I'm sure you can quickly translate it to Python.

#include <iostream>
#include <vector>

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


int main(int argc, char* argv[])
{
    // Load input image (3-channel)
    cv::Mat input = cv::imread(argv[1]);
    if (input.empty())
    {
        std::cout << "!!! failed imread()" << std::endl;
        return -1;
    }   

    // Convert the input to grayscale (1-channel)
    cv::Mat grayscale = input.clone();
    cv::cvtColor(input, grayscale, cv::COLOR_BGR2GRAY);

What grayscale looks like at this point:

    // Locate the black circular shape in the grayscale image
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(grayscale, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    // Fill the interior of the largest circular shape found with BLUE
    cv::Mat circular_shape = input.clone();
    for (size_t i = 0; i < contours.size(); i++)
    {
        std::vector<cv::Point> cnt = contours[i];
        double area = cv::contourArea(cv::Mat(cnt));        

        if (area > 500000 && area < 1000000) // magic numbers to detect the right circular shape
        {
            std::cout << "* Area: " << area << std::endl;
            cv::drawContours(circular_shape, contours, i, cv::Scalar(255, 0, 0), 
                             cv::FILLED, 8, std::vector<cv::Vec4i>(), 0, cv::Point() );
        }           
    }   

What circular_shape looks like at this point:

    // Create the output image with the same attributes of the original, i.e. dimensions & 3-channel, so we have a colored result at the end
    cv::Mat output = cv::Mat::zeros(input.size(), input.type());

    // copyTo() uses circular_shape as a mask and copies that exact portion of the input to the output
    input.copyTo(output, circular_shape);

    cv::namedWindow("Eggs", cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO);  
    cv::imshow("Eggs", output);
    cv::resizeWindow("Eggs", 800, 600);
    cv::waitKey(0);

    return 0;
}

The output displayed on the window is:

The advantage of this solution is that the user doesn't need to interact with the application to facilitate the detection of the eggs, since they are already painted in blue.

After this, other operations can be done on the output image such as cv::inRange() to isolate colored objects from the rest of the image.

So, for completion's sake I'll add a few more lines of text/code to demonstrate what you could do from this point forward to completely isolate the eggs from the rest of the image:

// Isolate blue pixels on the output image
cv::Mat blue_pixels_only;
cv::inRange(output, cv::Scalar(255, 0, 0), cv::Scalar(255, 0, 0), blue_pixels_only);

What blue_pixels_only looks like at this stage:

// Get rid of pixels on the edges of the shape 
int erosion_type = cv::MORPH_RECT; // MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE
int erosion_size = 3;
cv::Mat element = cv::getStructuringElement(erosion_type, 
                                            cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1), 
                                            cv::Point(erosion_size, erosion_size));
cv::erode(blue_pixels_only, blue_pixels_only, element);
cv::dilate(blue_pixels_only, blue_pixels_only, element);

cv::imshow("Eggs", blue_pixels_only);
cv::imwrite("blue_pixels_only.png", blue_pixels_only);

What blue_pixels_only looks like at this stage:

Community
  • 1
  • 1
karlphillip
  • 92,053
  • 36
  • 243
  • 426
  • Oh wow thank you very much, this is great! I do still have one problem though, which is that the large piece in the center needs to be ignored for me to get the best results (it's part of the device used to collect the eggs, so it's best to not select it). It is consistently in the center and very large by comparison, but doesn't always have the hole in the middle of it. Could you recommend a method of dealing with this? No need for code, just a general idea of how you would approach it. Thanks! – ahansen Nov 14 '13 at 22:43
  • 1
    I updated my answer with extra notes, and I'll write another comment telling you how to get rid of the center piece in a few minutes (I actually did that while I was writing the code). – karlphillip Nov 14 '13 at 22:51
  • 1
    Well, the technique to **remove** the center piece is actually the same we used here to **add** the largest circular shape. You just need to figure out what are the values for that `area`. There's a loop in code where the following is executed `if (area > 500000 && area < 1000000)`, right? You need to do the same logic but with different hardcoded values to paint the center piece in green. This will let you repaint the green area later as black. Feel free to click on the checkbox near my answer to select it as the official problem solver. – karlphillip Nov 14 '13 at 22:58