I am trying to analyze a picture. I am basically doing that in 2 steps:
My program works quite well in most cases but not all. That is mainly because I never really understood, how to convert the picture into black and white. I mainly copied code I found and made it work with trial and error(that's why I ended up, with greyscale and not b/w, since I didn't find, how to make every non-white pixel in the greyscale to black)
I am using PIL for my picture operations and my main functions for the picture operating look like this(convert() is used once and avgcol() is used for every part of the converted picture, I want to analyze)
def convert():
global im
matrix = (1.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 )
im = im.convert("L", matrix)
def avgcol(im):
p_colors=im.getcolors()
cnt_pix=(im.size[0])*(im.size[1])
avgpix=0
for i in range(len(p_colors)):
avgpix=avgpix+p_colors[i][0]*p_colors[i][1]
return round(avgpix/cnt_pix , 1)
I do not really know, what the matrix does(didn't find a site, which explains the single values). My specific problem right now, is that I'd like to convert pixels with too much green into white pixels, but I am pretty sure, that if that is solved, something else pops out, so some basic explaining, how something like that is done, would be great. But I am thankful for any pointer in the right direction. And please no solution with for-looping every pixel. The pictures are pretty big and the program should be fast.
If you look at the documentation, you'll see a few things.
First, convert
has a one-argument version, which only takes a mode
.
If you just want an RGB-to-greyscale conversion:
grey = im.convert("L")
The only reason to provide a matrix is if you don't want to use the default transformation.
And you can also convert directly to bilevel black&white:
bw = im.convert("1")
As the docs say:
When converting to a bilevel image (mode "1"), the source image is first converted to black and white. Resulting values larger than 127 are then set to white, and the image is dithered. To use other thresholds, use the point method.
So, if you want the standard threshold and dithering, just convert
. If you want a different threshold, it looks like this:
grey = im.convert("L")
table = [int(i>200) for i in range(256)]
bw = grey.point(table, '1')
In your answer, you say:
… how to make every non-white pixel in the greyscale to black
If you really mean "anything other than absolute pure white" as "non-white", you can't use point
for that. Even if you convert
to L16
and point
with a 65536-item table, that can't distinguish between 99.998% and 100% white, and it's conceivable that some of your original pixels that weren't white get matched wrong. So for that, you probably want to just iterate the original pixels directly (with the load
function) and build a new image manually.
As for what the matrix does… that's pretty simple if you understand the basics of matrix math. (If you don't, I can't explain it here, but the Wikipedia article may be a good starting place.)
Treat each source pixel as a vector (e.g., in an RGBA image, the vector <red, green, blue, alpha>
. Multiply that vector by a matrix, and you get a new vector. Treat that as a pixel in the destination colorspace.
For example, if you have D65 RGBA pixels, and you multiply each one by the 4x3 matrix given in the docs, the results are CIE XYZ pixels.
This means there is no reason to use a 4x3 matrix to convert to L. What you want is a 4x1 matrix. And then, only if you don't want the default. As the docs say:
When from a colour image to black and white, the library uses the ITU-R 601-2 luma transform:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
… which is just (.299, .587, .114, 0)
.
So, if you want to:
convert pixels with too much green into white pixels
That's somewhat ambiguous. But at least one way to do something like that is to just overemphasize the green. For example, if you use (.1495, .8448 , .0057, 0)
, green will be counted a lot more than usual (and red and blue a lot less) for determining brightness.
Meanwhile, at the end of your question:
And please no solution with for-looping every pixel. The pictures are pretty big and the program should be fast.
Under the covers, of course, a PIL command like bw = grey.point([int(i>200) for i in range(256)], '1')
is actually for-looping every pixel in grey
. It's just doing the loop in C rather than Python. And you can do the same thing yourself with Cython, or do it implicitly with numpy, or just use PyPy instead of your existing Python interpreter (which can JIT your Python for loop up to almost C speed).
Why would you want to do that? Well, look at the thresholding code each way:
# PIL
table = [int(i>200) for i in range(256)]
bw = grey.point(table, '1')
# Python (run in PyPy) or Cython
bw = [pixel > 200 for pixel in grey]
# numpy
bw = grey > 200