How to smooth the edges of this binary image of blood vessels obtained after thresholding.
I tried a method somewhat similar to this method but did not qui
You can dilate then erode the areas http://docs.opencv.org/2.4/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html.
import cv2
import numpy as np
blur=((3,3),1)
erode_=(5,5)
dilate_=(3, 3)
cv2.imwrite('imgBool_erode_dilated_blured.png',cv2.dilate(cv2.erode(cv2.GaussianBlur(cv2.imread('so-br-in.png',0)/255, blur[0], blur[1]), np.ones(erode_)), np.ones(dilate_))*255)
EDIT whith a scale facor off 4 before the stuff
Here is the result I obtained with your image:
My method is mostly based on several cv::medianBlur
applied on a scaled-up image.
Here is the code:
cv::Mat vesselImage = cv::imread(filename); //the original image
cv::threshold(vesselImage, vesselImage, 125, 255, THRESH_BINARY);
cv::Mat blurredImage; //output of the algorithm
cv::pyrUp(vesselImage, blurredImage);
for (int i = 0; i < 15; i++)
cv::medianBlur(blurredImage, blurredImage, 7);
cv::pyrDown(blurredImage, blurredImage);
cv::threshold(blurredImage, blurredImage, 200, 255, THRESH_BINARY);
The jagged edges are due to the thresholding. If you are comfortable with an output image that is non-binary (i.e. with 256 shades of grAy), you can just remove it and you get this image:
What you can do is increase the resolution of your image (e.g. double or triple it using resize
). After that, erosion and dilation as described in the other answer above will lead to finer results.
You most probably got a gray scale image of the blood vessels first and then thresholded. It still looks non-smooth because the original gray scale image had noise inside. Asking now for a smoothing of the edges will result in lower resolution. For example the dilution and erosion proposed in another answer might fuse neighboring vessels in the dilution step which then cannot be separated again in the erosion step.
It might be preferable to remove the noise in the gray scale image first (aka do a smoothing there) and do the thresholding as the last step.
Because you did not deliver the gray scale image I performed a mild smoothing (about one pixel width) here on the binary image and performed a thresholding again.
I did a smoothing (with a Gaussian kernel of fixed size) and a thresholding (with a thresholding parameter). I suggest you do that on the grayscale image data and adjust the two parameters until you like the result.
Matlab code in case it is of interest:
% read
img = imread('YyNQV.png');
img = double(img(:, :, 1) ~= 255); % png is RGB -> binary
% smooth
kernel = fspecial('gaussian', 10, 1.5);
kernel = kernel / sum(kernel(:)); % normalize to 1
img_smooth = conv2(img, kernel, 'same');
% binarize again
threshold = 0.4; % experiment with values between 0 and 1
img_smooth_threshold = img_smooth > threshold;
% save (exchange black and white)
imwrite(~img_smooth_threshold, 'YyNQV_smooth.png');
i made some modifications on @dhanushka 's answer for another question and get these images.
Sorry it is C++ code but maybe you will convert it to Python.
You can change the parameters below to get different results.
// contour smoothing parameters for gaussian filter
int filterRadius = 10; // you can try to change this value
int filterSize = 2 * filterRadius + 1;
double sigma = 20; // you can try to change this value
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, const char** argv )
{
Mat im = imread(argv[1], 0);
Mat cont = ~im;
Mat original = Mat::zeros(im.rows, im.cols, CV_8UC3);
Mat smoothed = Mat(im.rows, im.cols, CV_8UC3, Scalar(255,255,255));
// contour smoothing parameters for gaussian filter
int filterRadius = 5;
int filterSize = 2 * filterRadius + 1;
double sigma = 10;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
// find contours and store all contour points
findContours(cont, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, Point(0, 0));
for(size_t j = 0; j < contours.size(); j++)
{
// extract x and y coordinates of points. we'll consider these as 1-D signals
// add circular padding to 1-D signals
size_t len = contours[j].size() + 2 * filterRadius;
size_t idx = (contours[j].size() - filterRadius);
vector<float> x, y;
for (size_t i = 0; i < len; i++)
{
x.push_back(contours[j][(idx + i) % contours[j].size()].x);
y.push_back(contours[j][(idx + i) % contours[j].size()].y);
}
// filter 1-D signals
vector<float> xFilt, yFilt;
GaussianBlur(x, xFilt, Size(filterSize, filterSize), sigma, sigma);
GaussianBlur(y, yFilt, Size(filterSize, filterSize), sigma, sigma);
// build smoothed contour
vector<vector<Point> > smoothContours;
vector<Point> smooth;
for (size_t i = filterRadius; i < contours[j].size() + filterRadius; i++)
{
smooth.push_back(Point(xFilt[i], yFilt[i]));
}
smoothContours.push_back(smooth);
Scalar color;
if(hierarchy[j][3] < 0 )
{
color = Scalar(0,0,0);
}
else
{
color = Scalar(255,255,255);
}
drawContours(smoothed, smoothContours, 0, color, -1);
}
imshow( "result", smoothed );
waitKey(0);
}