I am looking for a perfect way to smooth edges of binary images. The problem is the binary image appears to be a staircase like borders which is very unpleasing for my further masking process.
I am attaching a raw binary image that is to be converted into smooth edges and I am also providing the expected outcome. I am also looking for a solution that would work even if we increase the dimensions of the image.
Problem Image Expected Outcome
To preserve the sharpness of a binary image, I would recommend applying something like a median filter. Here is an example of this:
from PIL import Image, ImageFilter
image = Image.open('input_image.png')
image = image.filter(ImageFilter.ModeFilter(size=13))
image.save('output_image.png')
which gives us the following results:
Figure 1. Left: The original input image. Right: The output image with a median filter of size 13.
Increasing the size of the filter would increase the degree of smoothing, but of course this comes as a trade-off because you also lose high-frequency information such as the sharp corner on the bottom-left of this sample image. Unfortunately, high-frequency features are similar in nature to the staircase-like borders.
You can do that in Python/OpenCV with the help of Skimage by blurring the binary image. Then apply a one-sided clip.
Input:
import cv2
import numpy as np
import skimage.exposure
# load image
img = cv2.imread('bw_image.png')
# blur threshold image
blur = cv2.GaussianBlur(img, (0,0), sigmaX=3, sigmaY=3, borderType = cv2.BORDER_DEFAULT)
# stretch so that 255 -> 255 and 127.5 -> 0
# C = A*X+B
# 255 = A*255+B
# 0 = A*127.5+B
# Thus A=2 and B=-127.5
#aa = a*2.0-255.0 does not work correctly, so use skimage
result = skimage.exposure.rescale_intensity(blur, in_range=(127.5,255), out_range=(0,255))
# save output
cv2.imwrite('bw_image_antialiased.png', result)
# Display various images to see the steps
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
You will have to adjust the amount of blur for the degree of aliasing in the image.
I have this python code that applies a series thresholding to an image of an eye so that it would it would be able to detect the pupil. I wrote this code using python 2.7 in windows 10. It actually worked great since I was able to get my desired output.
Here is the code that I wrote in windows 10:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('C:\Users\User\Documents\module4\input\left.jpg',0)
image = cv2.medianBlur(img,5)
#Apply Adaptive Threshold with Laplacian
th = cv2.adaptiveThreshold(image,255,cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,11,2)
laplacian = cv2.Laplacian(th,cv2.CV_64F)
cv2.imwrite('C:\Users\User\Documents\module4\output\output1.jpg', laplacian)
#Apply Inverse Binary Threshold
binthresh = cv2.imread('C:\Users\User\Documents\module4\output\output1.jpg',0)
ret,thresh2 = cv2.threshold(laplacian,127,255,cv2.THRESH_BINARY_INV)
cv2.imwrite('C:\Users\User\Documents\module4\output\output2.jpg', thresh2)
#Apply First Otsu's Threshold
otsuthresh1 = cv2.imread('C:\Users\User\Documents\module4\output\output2.jpg',0)
blur = cv2.GaussianBlur(otsuthresh1,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite('C:\Users\User\Documents\module4\output\output3.jpg', th3)
#Apply Gaussian Blur
gaussblur = cv2.imread('C:\Users\User\Documents\module4\output\output3.jpg',0)
blur2 = cv2.GaussianBlur(gaussblur,(5,5),0)
cv2.imwrite('C:\Users\User\Documents\module4\output\output4.jpg', blur2)
#Apply Second Otsu's Threshold
otsuthresh2 = cv2.imread('C:\Users\User\Documents\module4\output\output4.jpg',0)
blur3 = cv2.GaussianBlur(otsuthresh2,(5,5),0)
ret4,th4 = cv2.threshold(blur3,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#Apply Circular Hough Transform
circles = cv2.HoughCircles(th4,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(th4,(i[0],i[1]),i[2],(100,100,0),2)
# draw the center of the circle
cv2.circle(th4,(i[0],i[1]),2,(0,50,100),3)
cv2.imshow('combined', th4)
cv2.imwrite('C:\Users\User\Documents\module4\output\output5.jpg', th4)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is a screenshot of all the outputs of the code (including the original input image):
I tried running this same code in my raspberry pi, I just changed the file path of the input image as well as where to store the output images.
Here is the code that I ran in my Raspberry Pi:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('/home/pi/IPD/images/image1.jpg',0)
image = cv2.medianBlur(img,5)
#Apply Adaptive Threshold with Laplacian
th = cv2.adaptiveThreshold(image,255,cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,11,2)
laplacian = cv2.Laplacian(th,cv2.CV_64F)
#Apply Inverse Binary Threshold
binthresh = cv2.imread('/home/pi/IPD/temp/output1.jpg',0)
ret,thresh2 = cv2.threshold(binthresh,127,255,cv2.THRESH_BINARY_INV)
cv2.imwrite('/home/pi/IPD/temp/output2.jpg', thresh2)
#Apply First Otsu's Threshold
otsuthresh1 = cv2.imread('/home/pi/IPD/temp/output2.jpg',0)
blur = cv2.GaussianBlur(otsuthresh1,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite('/home/pi/IPD/temp/output3.jpg', th3)
#Apply Gaussian Blur
gaussblur = cv2.imread('/home/pi/IPD/temp/output3.jpg',0)
blur2 = cv2.GaussianBlur(gaussblur,(5,5),0)
cv2.imwrite('/home/pi/IPD/temp/output4.jpg', blur2)
#Apply Second Otsu's Threshold
otsuthresh2 = cv2.imread('C/home/pi/IPD/temp/output4.jpg',0)
blur3 = cv2.GaussianBlur(otsuthresh2,(5,5),0)
ret4,th4 = cv2.threshold(blur3,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#Apply Circular Hough Transform
circles = cv2.HoughCircles(th4,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(th4,(i[0],i[1]),i[2],(100,100,0),2)
# draw the center of the circle
cv2.circle(th4,(i[0],i[1]),2,(0,50,100),3)
cv2.imshow('combined', th4)
cv2.imwrite('/home/pi/IPD/images/final.jpg', th4)
cv2.waitKey(0)
cv2.destroyAllWindows()
However I get the following error:
Traceback (most recent call last):
File "/home/pi/IPD/mod4.py", line 18, in
ret,thresh2 = cv2.threshold(binthresh,127,255,cv2.THRESH_BINARY_INV)
error: /build/opencv-ISmtkH/opencv-2.4.9.1+dfsg/modules/core/src/matrix.cpp:269: error: (-215) m.dims >= 2 in function Mat
Actually, I've also encountered this error when I first wrote the code in windows 10 but I solved it by writing the newly thresholded image and just loading it again (as you can see in my code. I know it's an inefficient way) so that I can apply a new threshold to it. I've tried searching for possible explanations why this might be and I figured it has something to do with how many channels the signal I'm inputting is (I think). However, I'm still new to using opencv and image processing in general and I really don't understand the concept really that well (even though I've already researched it).
If you guys can help me and point me in the right direction, I would be really grateful. And also if you guys can suggest how I can avoid storing the newly thresholded image and loading it again (which is really an inefficient way of going about this) without causing any errors, I would really, really appreciate it.
I spotted 2 errors, I hope those are the only 2.
1) In your first code you have:
laplacian = cv2.Laplacian(th,cv2.CV_64F)
cv2.imwrite('C:\Users\User\Documents\module4\output\output1.jpg', laplacian)
#Apply Inverse Binary Threshold
binthresh = cv2.imread('C:\Users\User\Documents\module4\output\output1.jpg',0)
ret,thresh2 = cv2.threshold(laplacian,127,255,cv2.THRESH_BINARY_INV)
here you are doing the laplacian operator and saving it in a CV_64F image (doubles), but threshold ONLY takes CV_8U or CV_32F. Here you have two options, one is to change this 64F to 32F or to use the function normalize and convert it to 8U image. Something like:
cv2.normalize(laplacian, output1, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
2) In the second code you are missing:
cv2.imwrite('C:\Users\User\Documents\module4\output\output1.jpg', laplacian)
So, you are not saving such an image, thus you are not loading it either... no image, an error jumps out.
General suggestions, always use imshow to see what is going on until what point. Use relative paths for the saving and loading of the temp images, this way you only change the input path.
I am trying to recreate the tracings on an EKG and then overlay them onto a new grid but I am stuck with how to best trace the actual tracings. In the images that follow, there are 6 separate tracings I'd like to recreate on essentially a white background with a grid. Any help would be appreciated.
I have managed to find the edges and crop this from a jpg so all I am left with is this image:
I am trying to detect the tracings with either OpenCV's findContours or Hough Line transformations but my edge findings after a gaussian blur leaves me with: .. which isn't very helpful.
The hough lines look like this:
Can someone point me in the right direction? Thanks in advance.
Edit:
I did the Local Histogram and then a gaussian blur and another Canny edge detection. The local histogram image was:
and then the canny edge detection was:
You can try using the Sobel and Laplacian detectors as follows
img = cv2.imread('experiment.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img,(3,3),0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=1)
figure = plt.figure(figsize=(10,10))
sobel = figure.add_subplot(1,2,1)
sobel.imshow(sobelx,cmap='gray')
sobel.set_title('Sobel in x')
sobel.set_axis_off()
laplacianfig = figure.add_subplot(1,2,2)
laplacianfig.imshow(laplacian,cmap='gray')
laplacianfig.set_title('Laplacian')
laplacianfig.set_axis_off()
give you the following output
As you can see, the Sobel operator can be used to detect the lines. Maybe you can then plot those points where the pixel intensities are below the mean.
The image below shows an aerial photo of a house block (re-oriented with the longest side vertical), and the same image subjected to Adaptive Thresholding and Difference of Gaussians.
Images: Base; Adaptive Thresholding; Difference of Gaussians
The roof-print of the house is obvious (to the human eye) on the AdThresh image: it's a matter of connecting some obvious dots. In the sample image, finding the blue-bounded box below -
Image with desired rectangle marked in blue
I've had a crack at implementing HoughLinesP() and findContours(), but get nothing sensible (probably because there's some nuance that I'm missing). The python script-chunk that fails to find anything remotely like the blue box, is as follows:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# read in full (RGBA) image - to get alpha layer to use as mask
img = cv2.imread('rotated_12.png', cv2.IMREAD_UNCHANGED)
grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Otsu's thresholding after Gaussian filtering
blur_base = cv2.GaussianBlur(grey,(9,9),0)
blur_diff = cv2.GaussianBlur(grey,(15,15),0)
_,thresh1 = cv2.threshold(grey,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
thresh = cv2.adaptiveThreshold(grey,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
DoG_01 = blur_base - blur_diff
edges_blur = cv2.Canny(blur_base,70,210)
# Find Contours
(ed, cnts,h) = cv2.findContours(grey, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:4]
for c in cnts:
approx = cv2.approxPolyDP(c, 0.1*cv2.arcLength(c, True), True)
cv2.drawContours(grey, [approx], -1, (0, 255, 0), 1)
# Hough Lines
minLineLength = 30
maxLineGap = 5
lines = cv2.HoughLinesP(edges_blur,1,np.pi/180,20,minLineLength,maxLineGap)
print "lines found:", len(lines)
for line in lines:
cv2.line(grey,(line[0][0], line[0][1]),(line[0][2],line[0][3]),(255,0,0),2)
# plot all the images
images = [img, thresh, DoG_01]
titles = ['Base','AdThresh','DoG01']
for i in xrange(len(images)):
plt.subplot(1,len(images),i+1),plt.imshow(images[i],'gray')
plt.title(titles[i]), plt.xticks([]), plt.yticks([])
plt.savefig('a_edgedetect_12.png')
cv2.destroyAllWindows()
I am trying to set things up without excessive parameterisation. I'm wary of 'tailoring' an algorithm for just this one image since this process will be run on hundreds of thousands of images (with roofs/rooves of different colours which may be less distinguishable from background). That said, I would love to see a solution that 'hit' the blue-box target - that way I could at the very least work out what I've done wrong.
If anyone has a quick-and-dirty way to do this sort of thing, it would be awesome to get a Python code snippet to work with.
The 'base' image ->
Base Image
You should apply the following:
1. Contrast Limited Adaptive Histogram Equalization-CLAHE and convert to gray-scale.
2. Gaussian Blur & Morphological transforms (dialation, erosion, etc) as mentioned by #bad_keypoints. This will help you get rid of the background noise. This is the most tricky step as the results will depend on the order in which you apply (first Gaussian Blur and then Morphological transforms or vice versa) and the window sizes you choose for this purpose.
3. Apply Adaptive thresholding
4. Apply Canny's Edge detection
5. Find contour having four corner points
As said earlier you need to tweak with input parameters of these functions and also need to validate these parameters with other images. As it might be possible that it will work for this case but not for other cases. Based on trial and error you need to fix the parameter values.
I'm currently using morphology transformations on binary images with OpenCV 2.4
I just noticed that using the built-in functions of OpenCV, all my pixels' positions are shifted right and down by one (i.e. the pixel previously located at (i,j) is now located at (i+1, j+1))
import cv2
import numpy as np
from skimage.morphology import opening
image = cv2.imread('input.png', 0)
kernel = np.ones((16,16), np.uint8)
opening_opencv = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
opening_skimage = opening(image, kernel)
cv2.imwrite('opening_opencv.png', opening_opencv)
cv2.imwrite('opening_skimage.png', opening_skimage)
Input :
Output :
As I didn't understand why, I just tied the same operation using skimage, and it doesn't make this "gap" during the morphological transformation.
Ouput :
Any idea about this issue ?
Thanks !
It is the way you commented, but the exact inverse :)
Structuring elements with even size lead to shifts, there is no middle pixel. With an odd size, you get a middle pixel and (n-1)/2 pixels at each size.
Other way of saying it is that a SE with odd sizes is symmetric and even size is asymmetric.