I have an input image:
I use few functions to find contours in this image:
cv::Mat srcImg = cv::imread("input.png");
cv::Mat grayImg{};
cv::cvtColor(srcImg, grayImg, cv::COLOR_BGR2GRAY);
cv::Mat threshImg;
cv::adaptiveThreshold(grayImg, threshImg, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 11, 2);
My picture after thresholding is as below:
Now I want to find contours:
std::vector<std::vector<cv::Point>> ptContours{};
cv::findContours(threshImg, ptContours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
And here is my question: how can I use drawContours() to fill the inner area of the character A?
I want to achieve something like this:
CodePudding user response:
If you get the binary image via Otsu Thresholding, you get a nice binary blob without any "filling problems":
// Read the input image:
std::string imageName = "D://opencvImages//UZUd5.png";
cv::Mat testImage = cv::imread( imageName );
// Convert BGR to Gray:
cv::Mat grayImage;
cv::cvtColor( testImage, grayImage, cv::COLOR_RGB2GRAY );
// Get Binary via Otsu:
cv::Mat binaryImage;
cv::threshold( grayImage, binaryImage, 0, 255, cv::THRESH_OTSU );
This is the result:
If you don't want to use Otsu, and instead use your current approach, this is a possible way of filling the blob. It basically filters every contour by area and hierarchy. I look for the outmost and inmost contours, and perform some flood-fills accordingly - outside of the outer contour, to fill the canvas and inside the inner contour to fill the hole:
// Get Binary via Adaptive Thresh:
cv::Mat binaryImage;
cv::adaptiveThreshold( grayImage, binaryImage, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 11, 2 );
// Containers:
std::vector< std::vector<cv::Point> > contours;
std::vector< cv::Vec4i > hierarchy;
// Create a new matrix where things will be drawn:
cv::Mat filledBlob = cv::Mat::ones( binaryImage.size(), CV_8UC3 );
// Find contours:
cv::findContours(binaryImage, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
// Filling colors:
cv::Scalar fillColor = cv::Scalar( 0,0,0 );
cv::Scalar canvasColor = cv::Scalar( 255,255,255 );
for( int i = 0; i < (int)contours.size(); i ){
// Get Blob area:
float blobArea = cv::contourArea( contours[i] );
// Filter smaller blobs:
int minArea = 3000;
if ( blobArea > minArea) {
// Get contour heirarchy:
int contourHierarchy = hierarchy[i][3];
// Process the child contour:
if ( contourHierarchy != -1 ){
// Draw "hole":
cv::drawContours( filledBlob, contours, (int)i, fillColor, 1, cv::LINE_8, hierarchy, 0 );
// Get bounding rectangle:
cv::Rect bBox = cv::boundingRect(contours[i]);
// Compute centroid:
cv::Point centroid;
centroid.x = bBox.x 0.5*bBox.width;
centroid.y = bBox.y 0.5*bBox.height;
// Flood-fill at centroid with canvas color:
cv::floodFill( filledBlob, centroid, canvasColor, (cv::Rect*)0, cv::Scalar(), 0);
}else{
// Process the parent contour:
if ( contourHierarchy == -1 ){
// Draw outline:
cv::drawContours( filledBlob, contours, (int)i, fillColor, 1, cv::LINE_8, hierarchy, 0 );
// Flood-fill at canvas (outside of contour):
cv::floodFill( filledBlob, cv::Point( 1, 1 ), canvasColor, (cv::Rect*)0, cv::Scalar(), 0);
}
}
// Show image
cv::imshow( "Filled Blob", filledBlob );
cv::waitKey(0);
}
}
Which yields this image:
If you want an inverted image, just subtract 255 - filledBlob:
cv::subtract( 255, filledBlob, filledBlob );



