PIL filter pixels and paste - python

I am trying to paste an image onto another one. I am actually using the second answer by Joseph here because I am trying to do something very similar: resize my foregroud to the background image, and then copy only the black pixels in the foreground onto the background. My foreground is a color image with black contours, and I want only the contours to be pasted on the background. The line
mask = pixel_filter(mask, (0, 0, 0), (0, 0, 0, 255), (0, 0, 0, 0))
returns the error "image index out of range".
When I don't do this filtering process to see if pasting at least works, I get a "bad mask transparency error". I have set the background and foreground to RGB and RGBA both to see if any combination solves the problem, it doesn't.
What am I doing wrong in the mask() line, and what am I missing about the paste process? Thanks for any help.

The pixel filter function you are referencing has a slight bug it seems. It's trying to convert a 1 dimensional list index into a 2d index backwards. It should be (x,y) => (index/height, index%height) (see here). Below is the function (full attribution to the original author) rewritten.
def pixel_filter(image, condition, true_colour, false_colour):
filtered = Image.new("RGBA", image.size)
pixels = list(image.getdata())
for index, colour in enumerate(pixels):
if colour == condition:
filtered.putpixel((index/image.size[1],index%image.size[1]), true_colour)
else:
filtered.putpixel((index/image.size[1],index%image.size[1]), false_colour)
return filtered

Related

How to remove the empty images using python [duplicate]

Using the Python Imaging Library PIL how can someone detect if an image has all it's pixels black or white?
~Update~
Condition: Not iterate through each pixel!
if not img.getbbox():
... will test to see whether an image is completely black. (Image.getbbox() returns the falsy None if there are no non-black pixels in the image, otherwise it returns a tuple of points, which is truthy.) To test whether an image is completely white, invert it first:
if not ImageChops.invert(img).getbbox():
You can also use img.getextrema(). This will tell you the highest and lowest values within the image. To work with this most easily you should probably convert the image to grayscale mode first (otherwise the extrema might be an RGB or RGBA tuple, or a single grayscale value, or an index, and you have to deal with all those).
extrema = img.convert("L").getextrema()
if extrema == (0, 0):
# all black
elif extrema == (1, 1):
# all white
The latter method will likely be faster, but not so you'd notice in most applications (both will be quite fast).
A one-line version of the above technique that tests for either black or white:
if sum(img.convert("L").getextrema()) in (0, 2):
# either all black or all white
Expanding on Kindall:
if you look at an image called img with:
extrema = img.convert("L").getextrema()
It gives you a range of the values in the images. So an all black image would be (0,0) and an all white image is (255,255). So you can look at:
if extrema[0] == extrema[1]:
return("This image is one solid color, so I won't use it")
else:
# do something with the image img
pass
Useful to me when I was creating a thumbnail from some data and wanted to make sure it was reading correctly.
from PIL import Image
img = Image.open("test.png")
clrs = img.getcolors()
clrs contains [("num of occurences","color"),...]
By checking for len(clrs) == 1 you can verify if the image contains only one color and by looking at the second element of the first tuple in clrs you can infer the color.
In case the image contains multiple colors, then by taking the number of occurences into account you can also handle almost-completly-single-colored images if 99% of the pixles share the same color.
I tried the Kindall solution ImageChops.invert(img).getbbox() without success, my test images failed.
I had noticed a problem, white should be 255 BUT I have found white images where numeric extrema are (0,0).. why? See the update below.
I have changed Kindall second solution (getextrema), that works right, in a way that doesn't need image conversion, I wrote a function and verified that works with Grayscale and RGB images both:
def is_monochromatic_image(img):
extr = img.getextrema()
a = 0
for i in extr:
if isinstance(i, tuple):
a += abs(i[0] - i[1])
else:
a = abs(extr[0] - extr[1])
break
return a == 0
The img argument is a PIL Image object.
You can also check, with small modifications, if images are black or white.. but you have to decide if "white" is 0 or 255, perhaps you have the definitive answer, I have not. :-)
Hope useful
UPDATE: I suppose that white images with zeros inside.. may be PNG or other image format with transparency.

Using Python Pillow to 'Punch' Out transparency Using Second Image

I am currently trying to use an RGBA image to 'punch' out a hole in another RGBA image but all my current attempts have failed to maintain the original transparency. Once I apply an alpha channel using putalpha it will replace the original alpha channel completely and turn previously transparent pixels back to their original colors.
I am trying to perform a "putalpha" on only the pixels with 100% transparency.
In the photos below I attempt to overlap an 'inverted transparency' alpha channel on top of my Circle to perform the 'punch out'. Instead of only applying the transparent pixels it will replace the entire image's alpha which turns the rest of the circle image's transparency white.
Is there a way for me to do this transparency "Merge" to achieve an alpha layer that is a composite of both images?
#image2 is a square, image1 is a circle
# swapTransparency is a function I made that works in swapping the transparency, it just goes pixel by pixel and switches alpha channel value to # max where empty and to 0 everywhere else.
# probably a better and more effective way to invert the transparency but this works right now and might not even be needed.
def swapTransparency(img):
datas = img.getdata()
newData = []
for item in datas:
if item [3] == 0:
newData.append((0, 0, 0, 255))
else:
newData.append((255, 255, 255, 0))
img.putdata(newData)
return img
##This is putting alpha channel overtop but its replacing the entire alpha instead of merging them, losing original cricle transparency.
image2 = swapTransparency(image2)
alphaChannel = image2.getchannel('A')
image1.putalpha(image2)
Image1
Image2
Desired Results

how to draw outlines of objects on an image to a separate image

i am working on a puzzle, my final task here is to identify edge type of the puzzle piece.
as shown in the above image i have mange to rotate and crop out every edge of the piece in same angle. my next step is to separate the edge line into a separate image like as shown in the image bellow
then to fill up one side of the line with with a color and try to process it to decide what type of edge it is.
i dont see a proper way to separate the edge line from the image for now.
my approach::
one way to do is scan pixel by pixel and find the black pixels where there is a nun black pixel next to it. this is a code that i can implement. but it feels like a primitive and a time consuming approach.
so if there you can offer any help or ideas, or any completely different way to detect the hollows and humps.
thanks in advance..
First convert your color image to grayscale. Then apply a threshold, say zero to obtain a binary image. You may have to use morphological operations to further process the binary image if there are holes. Then find the contours of this image and draw them to a new image.
A simple code is given below, using opencv 4.0.1 in python 2.7.
bgr = cv2.imread('puzzle.png')
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
_, roi = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
cv2.imwrite('/home/dhanushka/stack/roi.png', roi)
cont = cv2.findContours(roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
output = np.zeros(gray.shape, dtype=np.uint8)
cv2.drawContours(output, cont[0], -1, (255, 255, 255))
# removing boundary
boundary = 255*np.ones(gray.shape, dtype=np.uint8)
boundary[1:boundary.shape[0]-1, 1:boundary.shape[1]-1] = 0
toremove = output & boundary
output = output ^ toremove

OpenCV - append part of image

I have a scanned document image of some black text on a white background. I first invert the image so background = black and all text = white.
I then use OpenCV's reduce() function to give me a vertical projection on the image, which looks a little like: (sum of all pixel values for each row in the image)
0,
0,
0,
434
34
0,
0,
From this, I can tell that 0 pixel values denote the background whereas values > 0 depict there is some text.
From here, I've looped through the pixel values looking for any 0 values in which the next value != 0 (The next value is contains text) and stored the positions in a two tuple list namely pairedCoordinates: [(23, 43), (54, 554)] etc...
From that point I can then loop through pairedCoordinates and draw boundingRects around my regions of interest:
for start, finish in pairedCoordinates:
height = finish - start
cv2.rectangle(small, (0, start), (0+cols, start + height), (255, 255, 255), 1)
Up to this point, all works fine. What I'm trying to do next, is for each rectangle, append its inner content (pixels) to another list so I can perform further computations on only sections of the image contained within the rects.
I've attempted the following:
# Initialise an empty list
roi = []
and then within the above for loop, I add the additional:
for start, finish in pairedCoordinates:
height = finish - start
cv2.rectangle(small, (0, start), (0+cols, start + height), (255, 255, 255), 1)
# cols = the number of columns in the image, small being the image
glyph = small[y: start + height, x:0+cols]
roi.append(glyph)
When attempting this code, I'm getting 'Unresolved references to x & y and I'm a little unsure why.
Could someone just point me in the right direction of how to actually achieve what I've explained above.
UPDATE
As mentioned by Miki in the comments, I forgot to initialise x, y.
I simply defined x=0 y=0 before computing the glyph and that seemed to do the trick.
I'm having some issues looping through each of the areas and writing them to a file though. Instead of each bounding rect being written individually, each new image file created is just appending the next pixels to the existing image?
for i, r in enumerate(roi):
cv2.imwrite("roi_%02d.png" % i, r)

How to overlay object with transparent color in OpenCV

Example
I will try to explain my question according the image. Firstly i use Python3 and OpenCV3. I just want to colorize the white pixels of mask(for example with shinny blue). Then using addWeighted, i want to blend that mask onto original image. But the problem i can't colorize the mask. Mask is the result of inRange fuction and i can't transform it to RGB.
https://www.youtube.com/watch?v=hQ-bpfdWQh8
Just like in the video but single frame.
For a quick mask visualization, try this:
debug_img = img/2 + mask/2
If img isn't grayscale already, replace img with img.mean(axis=2) or use cvtColor().
Another way is to use indexing:
debug_img = img.copy()
debug_img[mask>0] = (0, 255, 0) # replace masked pixels with green
To make the green transparent, simply add
debug_img = debug_img/2 + img/2

Categories