Correct method and python package that can find width of image function

spectrum

The input is a spectrum with colorful (sorry) vertical lines on a black background. Given the approximate x coordinate of this strip (as marked by X), I want to find the width of this strip.

I am not familiar with image processing. Please guide me to the correct Python image processing and image processing methodthat can do the same.


I think PIL, OpenCV gave me an idea of ​​being overkill for this particular application.

What if I want to make it an expert system that can classify them in the future?

+3


source to share


3 answers


I'll give a complete minimal working example (as suggested by sega_sai). I don't have access to the original image, but you'll see that it doesn't really matter! Peak distributions found in the following code:

Average values: 26.2840960523 80.8255092125



import Image
from scipy import *
from scipy.optimize import leastsq

# Load the picture with PIL, process if needed
pic         = asarray(Image.open("band2.png"))

# Average the pixel values along vertical axis
pic_avg     = pic.mean(axis=2)
projection  = pic_avg.sum(axis=0)

# Set the min value to zero for a nice fit
projection /= projection.mean()
projection -= projection.min()

# Fit function, two gaussians, adjust as needed
def fitfunc(p,x):
    return p[0]*exp(-(x-p[1])**2/(2.0*p[2]**2)) + \
        p[3]*exp(-(x-p[4])**2/(2.0*p[5]**2))
errfunc = lambda p, x, y: fitfunc(p,x)-y

# Use scipy to fit, p0 is inital guess
p0 = array([0,20,1,0,75,10])
X  = xrange(len(projection))
p1, success = leastsq(errfunc, p0, args=(X,projection))
Y = fitfunc(p1,X)

# Output the result
print "Mean values at: ", p1[1], p1[4]

# Plot the result
from pylab import *
subplot(211)
imshow(pic)
subplot(223)
plot(projection)
subplot(224)
plot(X,Y,'r',lw=5)
show()

      

enter image description here

+3


source


Below is a simple method for determining thresholds and their widths, it should work reliably enough for any number of rows. The yellow and black image below was processed using this script, the red / black graph illustrates the lines found using parameters threshold = 0.3, min_line_width = 5

)

enter image description here



The script averages the lines of the image and then determines the main start and end positions of each line based on the threshold (which you can set between 0 and 1) and the minimum line width (in pixels). Using the threshold value and minimum line width, you can easily filter the input images to draw lines from them. The first function find_lines

returns all the lines in the image as a list of tuples containing the start, end, center, and width of each line. The second function find_closest_band_width

is called with the specifiedx_position

and returns the width of the closest row to that position (assuming you want the distance to be the center for each row). Since the lines are saturated (255 cuts per channel), their cross-sections are not far from even distribution, so I don't think trying to fit any distribution will really help too much, just overcomplicating.

import Image, ImageStat

def find_lines(image_file, threshold, min_line_width):
    im = Image.open(image_file)
    width, height = im.size
    hist = []
    lines = []
    start = end = 0
    for x in xrange(width):
        column = im.crop((x, 0, x + 1, height))
        stat = ImageStat.Stat(column)
        ## normalises by 2 * 255 as in your example the colour is yellow
        ## if your images start using white lines change this to 3 * 255
        hist.append(sum(stat.sum) / (height * 2 * 255)) 

    for index, value in enumerate(hist):
        if value > threshold and end >= start:
            start = index
        if value < threshold and end < start:
            if index - start < min_line_width:
                start = 0
            else:
                end = index
                center = start + (end - start) / 2.0
                width = end - start
                lines.append((start, end, center, width))
    return lines

def find_closest_band_width(x_position, lines):
    distances = [((value[2] - x_position) ** 2) for value in lines]
    index = distances.index(min(distances))
    return lines[index][3]

## set your threshold, and min_line_width for finding lines
lines = find_lines("8IxWA_sample.png", 0.7, 4)
## sets x_position to 59th pixel
print 'width of nearest line:', find_closest_band_width(59, lines)

      

+2


source


I don't think you need anything out of the ordinary for you.

I would just use PIL + scipy. That should be enough.

Since you essentially need to take your image, make a 1D projection of it and then place it Gaussian or something. Information about the approximate location of the strip should be used for the preview.

+1


source







All Articles