Search code examples
javaandroidopencvimage-processingfeature-extraction

How to Remove Small Objects from Image after Segmentation


Description

I have a lung cancer CT scan image, that I want to segment and extract the cancerous areas from. I have used Open CV and Java.

I have the following image as input:

original image

After segmentation with thresholding and watershed method, I get this result:

original image

After that, I want to extract the cancerous area from the segmented image, so I have to remove all noise and other objects outside the region of interest (the cancerous nodule). So like shown in image below, I want to extract the cancerous nodule like this:

original image

How can I achieve this in android using OpenCV?


Solution

  • I tried to implement my suggested solution. My answer is in C++, but the idea is simple, you should be able to implement it in Java. As I commented, the idea is to use morphology to get the blob of interest. Mainly, the erode operation. Let's see:

       //Read input image:
       std::string imagePath = "C://opencvImages//lungsImage.png";
       cv::Mat imageInput= cv::imread( imagePath );
    
       //Convert it to grayscale:
       cv::Mat grayImg;
       cv::cvtColor( imageInput, grayImg, cv::COLOR_BGR2GRAY );
    

    The first step is to obtain a binary image. It seems you implemented Watershed segmentation. That's ok. I tried applying a simple adaptive thresholding with a big window (601 in size, for this case). It gave me good results:

        //Get the binary image:
        cv::adaptiveThreshold( grayImg, grayImg, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 601, 10 );
    

    This is the result you get:

    enter image description here

    Now, there are multiple blobs. However, I’ll be looking for the biggest blob, as that is where our target region of interest is located. Searching for the biggest blob in a binary image is a task I often carry out, so I have prepared a function for this. It is called findBiggestBlob. I’ll present the function later. Check out the result you get after filtering out the smaller blobs:

        //Get the biggest blob in the binary image
        cv::Mat targetBlobs = findBiggestBlob( grayImg );
    

    This is the result:

    enter image description here

    Now, simply apply morphology. First, an erode operation. Use an ellipse structuring element of size 5 x 5 with 4 iterations to detach the blob of interest:

        //Apply erosion to the biggest blob mask;
        cv::Mat morphKernel = cv::getStructuringElement( cv::MORPH_ELLIPSE, cv::Size(5, 5) );
        int morphIterations = 4; // use 4 iterations
        cv::morphologyEx( targetBlobs, targetBlobs, cv::MORPH_ERODE, morphKernel, cv::Point(-1,-1), morphIterations );
    

    Check out the result, the blob of interest is now detached:

    enter image description here

    Now, the idea is simple. If we, again, extract the biggest blob of the image, we should end up with the lungs, free from the cancerous area. Then, subtract this image to the “detached” mask, we should end up with the blob of interest in one mask:

        //Get the lungs image:
        cv::Mat bigBlob = findBiggestBlob( targetBlobs );
    

    You get this:

    enter image description here

        //Subtract the lungs from the first binary mask:
        cv::Mat blobOfInterest = targetBlobs - bigBlob;
    

    Now, let's restore the blob's original size applying a dilate operation, use the same structuring element and the same number of iterations. This is the result:

    enter image description here

        //Restore the blob's original size:
        cv::morphologyEx( blobOfInterest, blobOfInterest, cv::MORPH_DILATE, morphKernel, cv::Point(-1,-1), morphIterations );
    

    Here's the blob (in red) overlaid onto the original image:

    enter image description here

    This is the code for the findBiggestBlob function. The idea is just to compute all the contours in the binary input, calculate their area and store the contour with the largest area of the bunch:

    //Function to get the largest blob in a binary image:
    cv::Mat findBiggestBlob( cv::Mat &inputImage ){
    
        cv::Mat biggestBlob = inputImage.clone();
    
        int largest_area = 0;
        int largest_contour_index=0;
    
        std::vector< std::vector<cv::Point> > contours; // Vector for storing contour
        std::vector<cv::Vec4i> hierarchy;
    
        // Find the contours in the image
        cv::findContours( biggestBlob, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); 
    
        for( int i = 0; i< (int)contours.size(); i++ ) {            
    
            //Find the area of the contour            
            double a = cv::contourArea( contours[i],false);
            //Store the index of largest contour:
            if( a > largest_area ){
                largest_area = a;                
                largest_contour_index = i;
            }
    
        }
    
        //Once you get the biggest blob, paint it black:
        cv::Mat tempMat = biggestBlob.clone();
        cv::drawContours( tempMat, contours, largest_contour_index, cv::Scalar(0),
                      CV_FILLED, 8, hierarchy );
    
        //Erase the smaller blobs:
        biggestBlob = biggestBlob - tempMat;
        tempMat.release();
        return biggestBlob;
    }