loop binary image pixel - python

i have this image with two people in it. it is binary image only contains black and white pixels.
first i want to loop over all the pixels and find white pixels in the image.
than what i want to do is that i want to find [x,y] for the one certain white pixel.
after that i want to use that particular[x,y] in the image which is for the white pixel in the image.
using that co-ordinate of [x,y] i want to convert neighbouring black pixels into white pixels. not whole image tho.
i wanted to post image here but i cant post it unfortunately. i hope my question is understandable now. in the below image you can see the edges.
say for example the edge of the nose i find that with loop using [x,y] and than turn all neighbouring black pixels into white pixels.
This is the binary image

The operation described is called dilation, from Mathematical Morphology. You can either use, for example, scipy.ndimage.binary_dilation or implement your own.
Here are the two forms to do it (one is a trivial implementation), and you can check the resulting images are identical:
import sys
import numpy
from PIL import Image
from scipy import ndimage
img = Image.open(sys.argv[1]).convert('L') # Input is supposed to the binary.
width, height = img.size
img = img.point(lambda x: 255 if x > 40 else 0) # "Ignore" the JPEG artifacts.
# Dilation
im = numpy.array(img)
im = ndimage.binary_dilation(im, structure=((0, 1, 0), (1, 1, 1), (0, 1, 0)))
im = im.view(numpy.uint8) * 255
Image.fromarray(im).save(sys.argv[2])
# "Other operation"
im = numpy.array(img)
white_pixels = numpy.dstack(numpy.nonzero(im != 0))[0]
for y, x in white_pixels:
for dy, dx in ((-1,0),(0,-1),(0,1),(1,0)):
py, px = dy + y, dx + x
if py >= 0 and px >= 0 and py < height and px < width:
im[py, px] = 255
Image.fromarray(im).save(sys.argv[3])

Related

How can I convert an array of pixel colors into an image efficiently with Python?

I am working on a project that implements Numba and CUDA with Python 3.8. Currently, I create an array with the dimensions of the final image. Next, I generate an image with a CUDA kernel (incredibly fast). Then, I copy the pixel color into a Pillow Image (incredibly slow). My code:
for x in range(width):
for y in range(height):
if pixels[x][y] = 0:
color = [0, 0, 0]
else:
# Get color from int as tuple
color = rgb_conv(pixels[x][y]
red = int(numpy.floor(color[0]))
if red > 255:
red = 255
elif red < 0:
red = 0
green = int(numpy.floor(color[1]))
if green > 255:
green = 255
elif green < 0:
green = 0
blue = int(numpy.floor(color[2]))
if blue > 255:
blue = 255
elif blue < 0:
blue = 0
image.putpixel((x, y), (red, green, blue))
Are there any more efficient Python image libraries for this implementation? Is there a way to convert the array to an image on the GPU? Any help with direction works. Thanks!
EDIT 1: A request was made for the function rgb_conv. This is a function I found to convert a single integer into a three-wide color.
def rgb_conv(i):
color = 255 * numpy.array(colorsys.hsv_to_rgb(i / 255.0, 1.0, 0.5))
return tuple(color.astype(int))
However, I didn't particularly like the colors this function produces, so I removed it and began working with the following:
pixelArr = image.load()
for x in range(width):
for y in range(height):
color = int(numpy.floor(pixels[x][y]))
pixelArr[x, y] = (color << 21) + (color << 10) + color * 8
This adjustment doesn't do much to the running time of the code. I am looking further into a suggestion load the image from an array rather than putting each pixel into the image.
It is not efficient to place each pixel into an image with pillow. Creating an image from a numpy array is significantly faster than before. By faster, I mean the 3840x2160 image took minutes, but now takes 0.0117 seconds.
To create an array that can be converted to an image:
import numpy
pixels = numpy.zeros((height, width, 3), dtype=numpy.uint8)
Here, I run calculations on the GPU to create an image with the pixels array. To efficiently convert an array of pixels into an image:
from PIL import Image
image = Image.fromarray(pixels)

Image translation using numpy

I want to perform image translation by a certain amount (shift the image vertically and horizontally).
The problem is that when I paste the cropped image back on the canvas, I just get back a white blank box.
Can anyone spot the issue here?
Many thanks
img_shape = image.shape
# translate image
# percentage of the dimension of the image to translate
translate_factor_x = random.uniform(*translate)
translate_factor_y = random.uniform(*translate)
# initialize a black image the same size as the image
canvas = np.zeros(img_shape)
# get the top-left corner coordinates of the shifted image
corner_x = int(translate_factor_x*img_shape[1])
corner_y = int(translate_factor_y*img_shape[0])
# determine which part of the image will be pasted
mask = image[max(-corner_y, 0):min(img_shape[0], -corner_y + img_shape[0]),
max(-corner_x, 0):min(img_shape[1], -corner_x + img_shape[1]),
:]
# determine which part of the canvas the image will be pasted on
target_coords = [max(0,corner_y),
max(corner_x,0),
min(img_shape[0], corner_y + img_shape[0]),
min(img_shape[1],corner_x + img_shape[1])]
# paste image on selected part of the canvas
canvas[target_coords[0]:target_coords[2], target_coords[1]:target_coords[3],:] = mask
transformed_img = canvas
plt.imshow(transformed_img)
This is what I get:
For image translation, you can make use of the somewhat obscure numpy.roll function. In this example I'm going to use a white canvas so it is easier to visualize.
image = np.full_like(original_image, 255)
height, width = image.shape[:-1]
shift = 100
# shift image
rolled = np.roll(image, shift, axis=[0, 1])
# black out shifted parts
rolled = cv2.rectangle(rolled, (0, 0), (width, shift), 0, -1)
rolled = cv2.rectangle(rolled, (0, 0), (shift, height), 0, -1)
If you want to flip the image so the black part is on the other side, you can use both np.fliplr and np.flipud.
Result:
Here is a simple solution that translates an image by tx and ty pixels using only array indexing, that does not roll over, and handles negative values as well:
tx, ty = 8, 5 # translation on x and y axis, in pixels
N, M = image.shape
image_translated = np.zeros_like(image)
image_translated[max(tx,0):M+min(tx,0), max(ty,0):N+min(ty,0)] = image[-min(tx,0):M-max(tx,0), -min(ty,0):N-max(ty,0)]
Example:
(Note that for simplicity it does not handle cases where tx > M or ty > N).

How to make a shape larger or smaller without changing the resolution of the image using OpenCV or PIL in Python

I would like to be able to make a certain shape in either a PIL image or an OpenCV image 3 times larger and smaller without changing the resolution of the image or changing the shape of the shape I want to make larger. I have tried using OpenCV's dilation method but that is not it's intended use, plus it changed the shape of the image. For an example:
Thanks.
Here's a way of doing it:
find the interesting shape, i.e. non-white ROI area
extract it
scale it up by a factor
clear the original image to white
paste the scaled ROI back into image with same centre
#!/usr/bin/env python3
import cv2
import numpy as np
if __name__ == "__main__":
# Open image
orig = cv2.imread('image.png',cv2.IMREAD_COLOR)
# Get extent of interesting part, i.e. non-white part
y, x, _ = np.nonzero(~orig)
y0, y1 = np.min(y), np.max(y) # top and bottom rows
x0, x1 = np.min(x), np.max(x) # left and right cols
h, w = y1-y0, x1-x0 # height and width
ROI = orig[y0:y1, x0:x1] # extract ROI
cv2.imwrite('ROI.png', ROI) # DEBUG only
# Upscale ROI
factor = 3
scaledROI = cv2.resize(ROI, (w*factor,h*factor), interpolation=cv2.INTER_NEAREST)
newH, newW = scaledROI.shape[:2]
# Clear original image to white
orig[:] = [255,255,255]
# Get centre of original shape, and position of top-left of ROI in output image
cx, cy = (x0 + x1) //2, (y0 + y1)//2
top = cy - newH//2
left = cx - newW//2
# Paste in rescaled ROI
orig[top:top+newH, left:left+newW] = scaledROI
cv2.imwrite('result.png', orig)
That transforms this:
to this:
Puts me in mind of a pantograph:

How to analyze only a part of an image?

I want to analyse a specific part of an image, as an example I'd like to focus on the bottom right 200x200 section and count all the black pixels, so far I have:
im1 = Image.open(path)
rgb_im1 = im1.convert('RGB')
for pixel in rgb_im1.getdata():
Whilst you could do this with cropping and a pair of for loops, that is really slow and not ideal.
I would suggest you use Numpy as it is very commonly available, very powerful and very fast.
Here's a 400x300 black rectangle with a 1-pixel red border:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Open the image and make into Numpy array
im = Image.open('image.png')
ni = np.array(im)
# Declare an ROI - Region of Interest as the bottom-right 200x200 pixels
# This is called "Numpy slicing" and is near-instantaneous https://www.tutorialspoint.com/numpy/numpy_indexing_and_slicing.htm
ROI = ni[-200:,-200:]
# Calculate total area of ROI and subtract non-zero pixels to get number of zero pixels
# Numpy.count_nonzero() is highly optimised and extremely fast
black = 200*200 - np.count_nonzero(ROI)
print(f'Black pixel total: {black}')
Sample Output
Black pixel total: 39601
Yes, you can make it shorter, for example:
h, w = 200,200
im = np.array(Image.open('image.png'))
black = h*w - np.count_nonzero(ni[-h:,-w:])
If you want to debug it, you can take the ROI and make it into a PIL Image which you can then display. So just use this line anywhere after you make the ROI:
# Display image to check
Image.fromarray(ROI).show()
You can try cropping the Image to the specific part that you want:-
img = Image.open(r"Image_location")
x,y = img.size
img = img.crop((x-200, y-200, x, y))
The above code takes an input image, and crops it to its bottom right 200x200 pixels. (make sure the image dimensions are more then 200x200, otherwise an error will occur)
Original Image:-
Image after Cropping:-
You can then use this cropped image, to count the number of black pixels, where it depends on your use case what you consider as a BLACK pixel (a discrete value like (0, 0, 0) or a range/threshold (0-15, 0-15, 0-15)).
P.S.:- The final Image will always have a dimension of 200x200 pixels.
from PIL import Image
img = Image.open("ImageName.jpg")
crop_area = (a,b,c,d)
cropped_img = img.crop(crop_area)

Create a "spotlight" in an image using Python

Here's what I'm trying to do:
I have an image.
I want to take a circular region in the image, and have it appear as normal.
The rest of the image should appear darker.
This way, it will be as if the circular region is "highlighted".
I would much appreciate feedback on how to do it in Python.
Manually, in Gimp, I would create a new layer with a color of gray (less than middle gray). I would then create a circualr region on that layer, and make it middle gray. Then I would change the blending mode to soft light. Essentially, anything that is middle gray on the top layer will show up without modification, and anything darker than middle gray would show up darker.
(Ideally, I'd also blur out the top layer so that the transition isn't abrupt).
How can I do this algorithmically in Python? I've considered using the Pillow library, but it doesn't have these kinds of blend modes. I also considered using the Blit library, but I couldn't import (not sure it's maintained any more). Am open to scikit-image as well. I just need pointers on the library and some relevant functions.
If there's no suitable library, I'm open to calling command line tools (e.g. imagemagick) from within the Python code.
Thanks!
You can also do it using Python Image Library. This is a method that works, but might be optimized, since there is still a double for loop in it.
from PIL import Image
import math
def spotlight(img: Image, center: (int, int), radius: int) -> Image:
width, height = img.size
overlay_color = (0, 0, 0, 128)
img_overlay = Image.new(size=img.size, color=overlay_color, mode='RGBA')
for x in range(width):
for y in range(height):
dx = x - center[0]
dy = y - center[1]
distance = math.sqrt(dx * dx + dy * dy)
if distance < radius:
img_overlay.putpixel((x, y), (0, 0, 0, 0))
img.paste(img_overlay, None, mask=img_overlay)
return img
if __name__ == '__main__':
orig_file_name = 'amsterdam_1900x1500'
img = Image.open('{}.jpg'.format(orig_file_name))
spotlight_img = spotlight(img, (475, 900), 400)
spotlight_img.save('spotlight_{}.jpg'.format(orig_file_name))
Before:
After:
I finally did it with ImageMagick, using Python to calculate the various coordinates, etc.
This command will create the desired circle (radius 400, centered at (600, 600):
convert -size 1024x1024 xc:none -stroke black -fill steelblue -strokewidth 1 -draw "translate 600,600 circle 0,0 400,0" drawn.png
This command will then convert it to B/W to get a rudimentary mask:
convert drawn.png -alpha extract mask.png
This command will blur the mask (radius 180, sigma 16):
convert -channel RGBA -blur 100x16 mask.png mask2.png
The above three commands gives me the mask I need.
This command will darken the whole image (without the mask):
convert image.jpg -level 0%,130%,0.7 dark.jpg
And this command will put all 3 images together (original image, darkened image, and mask):
composite image.jpg dark.jpg mask2.png out.jpg

Categories