I'm currently trying to represent an image like the one below as a point defined path. Every "trace" should be a separate path. See picture below
The thing I'm trying to do right now is using scikit-image and scipy in python to fill up and skeletonize the image. See picture below
import os
from skimage import io, img_as_bool
from scipy import ndimage
from skimage.morphology import skeletonize
from skimage.util import invert
from matplotlib import pyplot as plt
filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'traces.png')
traces = io.imread(filename, True)
inverted = invert(traces)
boolimage = img_as_bool(inverted)
image = ndimage.binary_fill_holes(boolimage)
skeleton = skeletonize(image, method='lee')
plt.imshow(skeleton, cmap=plt.cm.gray)
plt.axis('off')
plt.show()
The skeleton however has many curving lines and I currently do not know how to proceed into cleaning it up into straight lines and angles so the lines endpoint can be used to record the paths. I have tried probabilistic Hough transform from scikit to obtain lines but these struggles with the bending nature of the lee skeleton and complete leaves out geometry.
Any help would greatly be appreciated, if you have any completely different ideas or some algorithm names those are also very much welcome!
I had the same problem to solve when I tried to reduce the thickness of letters from a font. I would suggest to take a look at an erosion algorithm (mathematical morphology). The algorithm is typically applied on binary images (two colors present in an image), therefore this is an image processing problem. This article might help a bit.
Related
I am doing a license-plate recognition. I have crop out the plate but it is very blurred. Therefore I cannot split out the digits/characters and recognize it.
Here is my image:
I have tried to denoise it through using scikit image function.
First, import the libraries:
import cv2
from skimage import restoration
from skimage.filters import threshold_otsu, rank
from skimage.morphology import closing, square, disk
then, I read the image and convert it to gray scale
image = cv2.imread("plate.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
I try to remove the noise:
denoise = restoration.denoise_tv_chambolle(image , weight=0.1)
thresh = threshold_otsu(denoise)
bw = closing(denoise > thresh, square(2))
What I got is :
As you can see, all the digits are mixed together. Thus, I cannot separate them and recognize the characters one by one.
What I expect is something like this (I draw it):
I am looking for help how can I better filter the image? Thank you.
=====================================================================
UPDATE:
After using skimage.morphology.erosion, I got:
First, this image seems to be more defaced by blur, than by noize, so there are no good reasons to denoise it, try debluring instead.
The simplest would be inverse filtering or even Wiener filtering. Then you'll need to separate image's background from letters by the level of luminosity for example with watershed algorithm. Then you'll get separate letters which you need to pass through one of classifiers for example, based on neural networks (even simplistic feed-forward net would be ok).
And then you'll finally get the textual representation. That's how such recognitions are usually made.
There's good book by Gonzalez&Woods, try looking for detailed explaination there.
I concur with the opinion that you should probably try to optimize the input image quality.
Number plate blur is a typical example of motion blur.
How well you can deblur depends upon how big or small is the blur radius.
Generally greater the speed of the vehicle, larger the blur radius and therefore more difficult to restore.
A simple solution that somewhat works is de-interlacing of images.
Note that it is only slightly more readable than your input image.
Here I have dropped every alternate line and resized the image to half its size using PIL/Pillow and this is what I get:
from PIL import Image
img=Image.open("license.jpeg")
size=list(img.size)
size[0] /= 2
size[1] /= 2
smaller_image=img.resize(size, Image.NEAREST)
smaller_image.save("smaller_image.png")
The next and more formal approach is deconvolution.
Since blurring is achieved using convolution of images, deblurring requires doing the inverse of convolution or deconvolution of the image. There are various kinds of deconvolution algorithms like the Wiener deconvolution,
Richardson-Lucy method, Radon transform and a few types of Bayesian filtering.
You can apply Wiener deconvolution algorithm using this code. Play with the angle, diameter and signal to noise ratio and see if it provides some improvements.
The skimage.restoration module also provides implementation of both unsupervised_wiener and richardson_lucy deconvolution.
In the code below I have shown both the implementations but you will have to modify the psf to see which one suits better.
import numpy as np
import matplotlib.pyplot as plt
import cv2
from skimage import color, data, restoration
from scipy.signal import convolve2d as conv2
img = cv2.imread('license.jpg')
licence_grey_scale = color.rgb2gray(img)
psf = np.ones((5, 5)) / 25
# comment/uncomment next two lines one by one to see unsupervised_wiener and richardson_lucy deconvolution
deconvolved, _ = restoration.unsupervised_wiener(licence_grey_scale, psf)
deconvolved = restoration.richardson_lucy(licence_grey_scale, psf)
fig, ax = plt.subplots()
plt.gray()
ax.imshow(deconvolved)
ax.axis('off')
plt.show()
Unfortunately most of these deconvolution alogirthms require you to know in advance the
blur kernel (aka the Point Spread Function aka PSF).
Here since you do not know the PSF, so you will have to use blind deconvolution.
Blind deconvolution attempts to estimate the original image without any knowledge of the blur kernel.
I have not tried this with your image but here is a Python implementation of blind deconvolution algorithm:
https://github.com/alexis-mignon/pydeconv
Note that an effective general purpose blind deconvolution algorithms has not yet been found and is an active field of research.
ChanVeseBinarize with an image enhanced binarized kernel gave me this result. This is helpful to highlight 4,8,1 and 2. I guess you need to do separate convolution with each character and if the peak of the convolution is higher than a threshold we can assume that letter to be present at the location of the peak. To take care of distortion, you need to do the convolution with few different types of font of a given character.
Another potential improvement using derivative filter and little bit of Gaussian smoothing. The K & X are not as distorted as the previous solution.
I'm having a little issue with something regarding plotting a fits image using matplotlib's imshow. It seems that my image is flipped both horizontally and vertically. I'm sure there is something simple I am overlooking, if anyone could point me in the right direction that would be great.
This is what my image should look like:
So, I'm loading my image as:
from astropy.io import fits
import matplotlib
import matplotlib.pyplot as pyplot
#Opening/reading in my fits file
hdulist = fits.open('.../myfits.fits')
#Accessing the image data and specifying the dimensions I wish to use
my_image = hdulist[0].data[0,0:,0:]
#Plotting the image
pyplot.imshow(image_SWIFT_uvm2_plot, cmap='gray', vmin=0, vmax=0.5)
pyplot.show()
This is what my image in the plot looks like (the plot is a little more complex than the code I have included, but I have given the critical lines as, hopefully, a self-sufficient code):
Those of you with keen eyes should see that the image has flipped both horizontally and vertically.
For FITS files the convention is that the origin is at the lower left hand corner of the image, so you need to use origin='lower' (by default Matplotlib uses origin='upper').
I have never used the astropy module, but I know that PyFITS opens the image data as a NumPy array (and from what I'm reading, astropy.io.fits has inherited the functionality of PyFITS anyway, so it should work the same way). If that is the case, then you may use numpy.fliplr and numpy.flipud to flip the array to your desired orientation. Just replace the line
pyplot.imshow(image_SWIFT_uvm2_plot, cmap='gray', vmin=0, vmax=0.5)
with
import numpy as np
pyplot.imshow(np.fliplr(np.flipud(image_SWIFT_uvm2_plot)), cmap='gray',
vmin=0, vmax=0.5)
Alternatively, you could do a little linear algebra to flip it, or just note that performing both of these flips is the same as using np.rot90 twice
pyplot.imshow(np.rot90(image_SWIFT_uvm2_plot, k=2), cmap='gray', vmin=0, vmax=0)
everyone i'm fairly new to OpenCV and computer vision and i'm stuck at this problem , which might seem like a fairly trivial but forgive my noobness :)
I'm trying to detect Rebars from a cross-sectional image.
i'm using this code :
import cv2
import cv2.cv as cv
import numpy as np
img = cv2.imread('test/t2.jpg',0)
img = cv2.equalizeHist(img)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,1,10,param1=50,param2=30,minRadius=0,maxRadius=25)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
This is the result i'm getting currently, which is not good :
I'm looking for pointers on how to proceed with this problem and how to learn more about CV as i'm really interested!
Thanks a ton!
HoughCircles is not a strong enough way to detect circle in such complex image like your case.
SO has already had some discussion about this. You could refer these post with quality accepted answers
Standard way:
Filled circle detection using CV2 in Python?
What are the possible fast ways to detect circle in an image?
Noise image:
https://dsp.stackexchange.com/questions/5930/find-circle-in-noisy-data
Another method:
Gradient Pair Vectors
Learning Automata
Those results can be slightly improved with setting the parameters better on this line:
circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,1,10,param1=50,param2=30,minRadius=0,maxRadius=25)
For example, you can reduce the maxRadius slightly and increase the sensitivity.
In my experience, however, you won't get a good result on an image like this. It is very complex, the circles are irregular and at different angles. If your goal is to practice, then sure, play with the parameters and try different methods to improve it. I don't see much practical use though.
You can detect features here, using the module trackpy. You need to vary feature sizes with odd numbers and see which one matches best. You may also need to do some pre-processing like, converting image to grayscale.
import trackpy as tp
import numpy as np
import pandas as pd
import pims
import matplotlib.pyplot as plt
#%% importing the data
frames=pims.ImageSequence('F:/TrapHysteresis/processing/Positions/*.TIF')
#%% tracking circles and center positions
featuresize=71
f1=tp.locate(frames[0],featuresize)
plt.figure()
tp.annotate(f1,frames[0])
I am working on a project that requires detecting lines on a plate of sand. The lines are hand-drew by user so that are not exactly "straight" (see photo). And because of the sand, the lines are quite hard to distinguish.
I tried cv2.HoughLines from OpenCV but didn't achieve good results. So any suggestion on the detecting method? And welcome for suggestion to improve the clarity of the lines. I am thinking of putting a few led light surrounding the plate.
Thanks
The detecting method depends a lot on how much generality you require: is the exposure and contrast going to change from one image to another? Is the typical width of lines going to change? In the following, I assume that such parameters do not vary much for your applications, please correct me if I'm wrong.
I'll be using scikit-image, a common image processing package for Python. If you're not familiar with this package, documentation can be found on http://scikit-image.org/, and the package is bundled with all installations of Scientific Python. However, the algorithms that I use are also available in other tools, like opencv.
My solution is written below. Basically, the principle is
first, denoise the image. Life is usually simpler after a denoising step. Here I use a total variation filter, since it results in a piecewise-constant image that will be easier to threshold. I enhance dark regions using a morphological erosion (on the gray-level image).
then apply an adaptive threshold that varies locally in space, since the contrast varies through the image. This operation results in a binary image.
erode the binary image to break spurious links between regions, and keep only large regions.
compute a measure of the elongation of the regions to keep only the most elongated ones. Here I use the ratio of the eigenvalues of the inertia tensor.
Parameters that are the most difficult to tune is the block size for the adaptive thresholding, and the minimum size of regions to keep. I also tried a Canny filter on the denoised image (skimage.filters.canny), and results were quite good, but edges were not always closed, you might also want to try an edge-detection method such as a Canny filter.
The result is shown below:
# Import modules
import numpy as np
from skimage import io, measure, morphology, restoration, filters
from skimage import img_as_float
import matplotlib.pyplot as plt
# Open the image
im = io.imread('sand_lines.png')
im = img_as_float(im)
# Denoising
tv = restoration.denoise_tv_chambolle(im, weight=0.4)
ero = morphology.erosion(tv, morphology.disk(5))
# Threshold the image
binary = filters.threshold_adaptive(ero, 181)
# Clean the binary image
binary = morphology.binary_dilation(binary, morphology.disk(8))
clean = morphology.remove_small_objects(np.logical_not(binary), 4000)
labels = measure.label(clean, background=0) + 1
# Keep only elongated regions
props = measure.regionprops(labels)
eigvals = np.array([prop.inertia_tensor_eigvals for prop in props])
eigvals_ratio = eigvals[:, 1] / eigvals[:, 0]
eigvals_ratio = np.concatenate(([0], eigvals_ratio))
color_regions = eigvals_ratio[labels]
# Plot the result
plt.figure()
plt.imshow(color_regions, cmap='spectral')
I want to display an image file using imshow. It is an 1600x1200 grayscale image and I found out that matplotlib uses float32 to decode the values. It takes about 2 seconds to load the image and I would like to know if there is any way to make this faster. The point is that I do not really need a high resolution image, I just want to mark certain points and draw the image as a background. So,
First question: Is 2 seconds a good performance for such an image or
can I speed up.
Second question: If it is good performance how can I make the process
faster by reducing the resolution. Important point: I still want the
image to strech over 1600x1200 Pixel in the end.
My code:
import matplotlib
import numpy
plotfig = matplotlib.pyplot.figure()
plotwindow = plotfig.add_subplot(111)
plotwindow.axis([0,1600,0,1200])
plotwindow.invert_yaxis()
img = matplotlib.pyplot.imread("lowres.png")
im = matplotlib.pyplot.imshow(img,cmap=matplotlib.cm.gray,origin='centre')
plotfig.set_figwidth(200.0)
plotfig.canvas.draw()
matplotlib.pyplot.show()
This is what I want to do. Now if the picture saved in lowres.png has a lower resolution as 1600x1200 (i.e. 400x300) it is displayed in the upper corner as it should. How can I scale it to the whole are of 1600x1200 pixel?
If I run this program the slow part comes from the canvas.draw() command below. Is there maybe a way to speed up this command?
Thank you in advance!
According to your suggestions I have updated to the newest version of matplotlib
version 1.1.0svn, checkout 8988
And I also use the following code:
img = matplotlib.pyplot.imread(pngfile)
img *= 255
img2 = img.astype(numpy.uint8)
im = self.plotwindow.imshow(img2,cmap=matplotlib.cm.gray, origin='centre')
and still it takes about 2 seconds to display the image... Any other ideas?
Just to add: I found the following feature
zoomed_inset_axes
So in principle matplotlib should be able to do the task. There one can also plot a picture in a "zoomed" fashion...
The size of the data is independent of the pixel dimensions of the final image.
Since you say you don't need a high-resolution image, you can generate the image quicker by down-sampling your data. If your data is in the form of a numpy array, a quick and dirty way would be to take every nth column and row with data[::n,::n].
You can control the output image's pixel dimensions with fig.set_size_inches and plt.savefig's dpi parameter:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
data=np.arange(300).reshape((10,30))
plt.imshow(data[::2,::2],cmap=cm.Greys)
fig=plt.gcf()
# Unfortunately, had to find these numbers through trial and error
fig.set_size_inches(5.163,3.75)
ax=plt.gca()
extent=ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
plt.savefig('/tmp/test.png', dpi=400,
bbox_inches=extent)
You can disable the default interpolation of imshow by adding the following line to your matplotlibrc file (typically at ~/.matplotlib/matplotlibrc):
image.interpolation : none
The result is much faster rendering and crisper images.
I found a solution as long as one needs to display only low-resolution images. One can do so using the line
im = matplotlib.pyplot.imshow(img,cmap=matplotlib.cm.gray, origin='centre',extent=(0,1600,0,1200))
where the extent-parameter tells matplotlib to plot the figure over this range. If one uses an image which has a lower resolution, this speeds up the process quite a lot. Nevertheless it would be great if somebody knows additional tricks to make the process even faster in order to use a higher resolution with the same speed.
Thanks to everyone who thought about my problem, further remarks are appreciated!!!