c++opencvwatershed

C++ and OpenCV 4.5.3 - (-215: Assertion failed)


Problem : Watershed algorithm

I started app project, for image processing, using OpenCv 4.5.3 and Swift ( with C++ ). I'm fighting with watershaded alg. for a really long time... And i have no clue what did i do wrong. Just don't know...

Error :

libc++abi.dylib: terminating with uncaught exception of type cv::Exception: OpenCV(4.5.3)
/Volumes/build-storage/build/master_iOS-mac/opencv/modules/imgproc/src/segmentation.cpp:161: 
error: (-215:Assertion failed) src.type() 
== CV_8UC3 && dst.type() == CV_32SC1 in function 'watershed'

terminating with uncaught exception of type cv::Exception: OpenCV(4.5.3) 
/Volumes/build-storage/build/master_iOS-mac/opencv/modules/imgproc/src/segmentation.cpp:161: error: 
(-215:Assertion failed) src.type() 
== CV_8UC3 && dst.type() == CV_32SC1 in function 'watershed'

In the definition of openCv's watershed we can find :

@param image Input 8-bit 3-channel image.
@param markers Input/output 32-bit single-channel image (map) of markers. It should have the same size as image .

Code

+(UIImage *) watershed:(UIImage *)src{
 cv::Mat img, mask;
    UIImageToMat(src, img);
    // Change the background from white to black, since that will help later to extract
      // better results during the use of Distance Transform
    cv::inRange(img, cv::Scalar(255,255,255), cv::Scalar(255,255,255), mask);
    img.setTo(cv::Scalar(0,0,0), mask);
    
    // Create a kernel that we will use to sharpen our image
    // an approximation of second derivative, a quite strong kernel
    cv::Mat kernel = (cv::Mat_<float>(3,3) <<
                      1, 1, 1,
                      1, -8, 1,
                      1, 1, 1);
    
    // do the laplacian filtering as it is
        // well, we need to convert everything in something more deeper then CV_8U
        // because the kernel has some negative values,
        // and we can expect in general to have a Laplacian image with negative values
        // BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
        // so the possible negative number will be truncated
    cv::Mat lapl;
    cv::filter2D(img, lapl, CV_32F, kernel);
    
    cv::Mat sharp;
    img.convertTo(sharp, CV_32F);
    cv::Mat result = sharp - lapl;
    
    // convert back to 8bits gray scale
    result.convertTo(result, CV_8UC3);
    lapl.convertTo(lapl, CV_8UC3);
    
    cv::Mat bw;
    cv::cvtColor(result, bw, cv::COLOR_BGR2GRAY);
    cv::threshold(bw, bw, 40, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    
    // Perform the distance transform algorithm
      cv::Mat dist;
      cv::distanceTransform(bw, dist, cv::DIST_L2, cv::DIST_MASK_3);
    
    // Normalize the distance image for range = {0.0, 1.0}
     // so we can visualize and threshold it
    cv::normalize(dist, dist, 0, 1.0, cv::NORM_MINMAX);
    
    // Threshold to obtain the peaks
      // This will be the markers for the foreground objects
      cv::threshold(dist, dist, 0.4, 1.0, cv::THRESH_BINARY);
    
    // Dilate a bit the dist image
    cv::Mat kernel1 = cv::Mat::ones(3, 3, CV_8U);
      dilate(dist, dist, kernel1);
   
      // Create the CV_8U version of the distance image
      // It is needed for findContours()
    cv::Mat dist_8u;
      dist.convertTo(dist_8u, CV_8U);
      // Find total markers
      std::vector<std::vector<cv::Point> > contours;
    findContours(dist_8u, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
      // Create the marker image for the watershed algorithm
    cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32S);
      // Draw the foreground markers
      for (size_t i = 0; i < contours.size(); i++)
      {
          drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
      }
      // Draw the background marker
      circle(markers, cv::Point(5,5), 3, cv::Scalar(255), -1);
      cv::Mat markers8u;
      markers.convertTo(markers8u, CV_8U, 10);
      
      // Perform the watershed algorithm
      watershed(result, markers);
     
     
    return MatToUIImage(result);
}

You can clearly see, that variables has proper type, as in descr. of function:

 result.convertTo(result, CV_8UC3);
cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32S);

Solution

  • The convertTo can not add channels as well can not reduce/convert image to image with smaller amount of channels. The key in this case is to use :

     cvtColor(src, src, COLOR_BGRA2BGR); // change 4 to 3 channels