How do I create a checker board pattern? - python

I am trying to write a code that can generate an checker board pattern. The final image size should be 100 x 100, the checker board size 5 x 5, such that each box has dimensions h/5 and w/5. The code I have is wrong:
from PIL import Image, ImageDraw
h = 500
w = 500
img = Image.new("RGB", (h,w), (255, 0, 0)) # create a new 15x15 image
pixels = img.load() # create the pixel map
print("1")
for i in range (h):
for j in range(w):
if ((i + j)%2) != 0:
im = Image.new('RGB', (h//5, w//5), 'black')
else:
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((h//5, w//5), (i+.5, j+.5)), fill="blue")
print ("done")
img.show()

I know it's already been answered, here's a way to loop over it differently
from PIL import Image
h = 500
w = 500
# You can also easily set the number of squares per row
number_of_square_across = 10
# You can also easily set the colors
color_one = (0, 0, 0)
color_two = (0, 0, 255)
length_of_square = h/number_of_square_across
length_of_two_squares = h/number_of_square_across*2
img = Image.new("RGB", (h, w), (255, 0, 0)) # create a new 15x15 image
pixels = img.load() # create the pixel map
for i in range(h):
# for every 100 pixels out of the total 500
# if its the first 50 pixels
if (i % length_of_two_squares) >= length_of_square:
for j in range(w):
if (j % length_of_two_squares) < length_of_square:
pixels[i,j] = color_one
else:
pixels[i,j] = color_two
# else its the second 50 pixels
else:
for j in range(w):
if (j % length_of_two_squares) >= length_of_square:
pixels[i,j] = color_one
else:
pixels[i,j] = color_two
print("done")
img.show()

For anyone who reads this in the future and is looking for a general solution to make a checker board see here:
M, N = 10, 10
arr = [[0 for _ in range(N)] for _ in range(M)] # an M by N array
for i in range(M):
for j in range(N):
if (i&1)^(j&1): # if (i is odd and j is even) or (j is odd and i is even)
arr[i][j] = 1 # change the pixel from white to black
Solution tailored specifically to the OP's inquiry see here:
from PIL import Image, ImageDraw
h = 500
w = 500
img = Image.new("RGB", (h,w), (255, 0, 0)) # create a new 15x15 image
pixels = img.load() # create the pixel map
print ("1")
for i in range (h):
for j in range(w):
if (i&1)^(j&1):
pixels[i,j] = (0, 0, 0)
else:
pixels[i,j] = (0, 0, 255)
print ("done")
img.show()
# BIGGER BOX SIZE
from PIL import Image, ImageDraw
h = 500
w = 500
img = Image.new("RGB", (h,w), (255, 0, 0)) # create a new 15x15 image
pixels = img.load() # create the pixel map
print ("1")
box_size = 25
for i in range (0, h, box_size):
for j in range(0, w, box_size):
y, x = i // box_size, j // box_size
if (y&1)^(x&1):
for di in range(box_size):
for dj in range(box_size):
pixels[i+di,j+dj] = (0, 0, 0)
else:
for di in range(box_size):
for dj in range(box_size):
pixels[i+di,j+dj] = (0, 0, 255)
print ("done")
img.show()
This will get you the checker pattern for an array.

I had the feeling, that accessing single pixels in Pillow using all those nested loops isn't the best idea performance-wise.
So, my idea would be to set up a small (m, n) checker using (nested) lists, build a Pillow Image object from that using putdata, and simply resize it to the desired size using the Image.NEAREST resampling filter. I added some flexibility for choosing colors and image modes.
from itertools import chain
from math import ceil
from PIL import Image
m, n = (5, 5) # Checker dimension (x, y)
w, h = (100, 100) # Final image width and height
c1 = 0 # (224, 64, 64) # First color
c2 = 255 # (128, 128, 128) # Second color
mode = 'L' if isinstance(c1, int) else 'RGB' # Mode from first color
# Generate pixel-wise checker, even x dimension
if m % 2 == 0:
pixels = [[c1, c2] for i in range(int(m/2))] + \
[[c2, c1] for i in range(int(m/2))]
pixels = [list(chain(*pixels)) for i in range(ceil(n/2))]
# Generate pixel-wise checker, odd x dimension
else:
pixels = [[c1, c2] for i in range(ceil(m*n/2))]
# Generate final Pillow-compatible pixel values
pixels = list(chain(*pixels))[:(m*n)]
# Generate Pillow image from pixel values, resize to final image size, and save
checker = Image.new(mode, (m, n))
checker.putdata(pixels)
checker = checker.resize((w, h), Image.NEAREST)
checker.save('checker.png')
For the shown configuration, the output would be:
Switching to RGB, and altering m, w, and h, we might get something like this:
As already pointed out in the other answers, if w or h are not whole integer factors of m or n, then you'll get somehow distorted output (m = n = 5, w = h = 102):
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
Pillow: 8.1.2
----------------------------------------

Related

Count number of White Pixels per row of an image in Python

I am stuck trying to count the number of white pixels per row in a image.
i have tried this
img = cv2.imread("11.jpg") # Its our Image object
mask = np.uint8(np.where(img == 0, 1, 0))
row_counts = cv2.reduce(mask, 1, cv2.REDUCE_SUM, dtype=cv2.CV_32SC1) # type: ignore
#Data Collection
White_row = 224- row_counts # 224 is max pixels in row
So i made my own loop algorithm that loops through every pixel and checks the pixel intensity. The image is converted into grey scale.
img = Image.open("11.jpg")
img = img.convert("L") # convert to grayscale
pix = img.load()
width, height = img.size
print(width, height)
print(pix[91,134]) #Tester
White_row = [0] * height
for y in range(height):
for x in range(width):
if pix[x,y] > 235: # 235 is the threshold
White_row[y] += 1
Max_Width = np.max(White_row)
Mean_Width = np.mean(White_row)
print ("Max_Width: ", Max_Width)
print ("Mean_Width: ", Mean_Width)

How to rotate an image to get not-null pixels?

In the image I linked below, I need to get all the yellow/green pixels in this rotated rectangle and get rid of the blue background, so that the rectangle's axis are aligned with the x and y axis.
I'm using numpy but don't have a clue what I should do.
I uploaded the array in this drive in case anyone would like to work with the actual array
Thanks for the help in advance.
I used the same image as user2640045, but different approach.
import numpy as np
import cv2
# load and convert image to grayscale
img = cv2.imread('image.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# binarize image
threshold, binarized_img = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# find the largest contour
contours, hierarchy = cv2.findContours(binarized_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
c = max(contours, key = cv2.contourArea)
# get size of the rotated rectangle
center, size, angle = cv2.minAreaRect(c)
# get size of the image
h, w, *_ = img.shape
# create a rotation matrix and rotate the image
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated_img = cv2.warpAffine(img, M, (w, h))
# crop the image
pad_x = int((w - size[0]) / 2)
pad_y = int((h - size[1]) / 2)
cropped_img = rotated_img[pad_y : pad_y + int(size[1]), pad_x : pad_x + int(size[0]), :]
Result:
I realize there is a allow_pickle=False option in numpys load method but I didn't feel comfortable with unpickling/using data from the internet so I used the small image. After removing the coordinate system and stuff I had
I define two helper methods. One to later rotate the image taken from an other stack overflow thread. See link below. And one to get a mask being one at a specified color and zero otherwise.
import numpy as np
import matplotlib.pyplot as plt
import sympy
import cv2
import functools
color = arr[150,50]
def similar_to_boundary_color(arr, color=tuple(color)):
mask = functools.reduce(np.logical_and, [np.isclose(arr[:,:,i], color[i]) for i in range(4)])
return mask
#https://stackoverflow.com/a/9042907/2640045
def rotate_image(image, angle):
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
return result
Next I calculate the angle to rotate about. I do that by finding the lowest pixel at width 50 and 300. I picked those since they are far enough from the boundary to not be effected by missing corners etc..
i,j = np.where(~similar_to_boundary_color(arr))
slope = (max(i[j == 50])-max(i[j == 300]))/(50-300)
angle = np.arctan(slope)
arr = rotate_image(arr, np.rad2deg(angle))
plt.imshow(arr)
.
One way of doing the cropping is the following. You calculate the mid in height and width. Then you take two slices around the mid say 20 pixels in one direction and to until the mid in the other one. The biggest/smallest index where the pixel is white/background colored is a reasonable point to cut.
i,j = np.where(~(~similar_to_boundary_color(arr) & ~similar_to_boundary_color(arr, (0,0,0,0))))
imid, jmid = np.array(arr.shape)[:2]/2
imin = max(i[(i < imid) & (jmid - 10 < j) & (j < jmid + 10)])
imax = min(i[(i > imid) & (jmid - 10 < j) & (j < jmid + 10)])
jmax = min(j[(j > jmid) & (imid - 10 < i) & (i < imid + 10)])
jmin = max(j[(j < jmid) & (imid - 10 < i) & (i < imid + 10)])
arr = arr[imin:imax,jmin:jmax]
plt.imshow(arr)
and the result is:

How to get a floodfilled area and its borders in an image with Python OpenCV?

I have an image such as this one, which is only black and white:
I would like to obtain only the flooded area of the image with the border using cv2.floodfill, like so (pardon my Paint skills):
Here's my current code:
# Copy the image.
im_floodfill = cv2.resize(actual_map_image, (500, 500)).copy()
# Floodfill from point (X, Y)
cv2.floodFill(im_floodfill, None, (X, Y), (255, 255, 255))
# Display images.
cv2.imshow("Floodfilled Image", im_floodfill)
cv2.waitKey(0)
The output I get is equal to the original image. How can I get only the flooded area with borders?
EDIT: I want to floodfill from any white point inside the "arena", like the red dot (X,Y) in the image. I wish to have only the outer border of the small circles inside the arena and the inner border of the outside walls.
EDIT2: I'm halfway there with this:
# Resize for test purposes
actual_map_image = cv2.resize(actual_map_image, (1000, 1000))
actual_map_image = cv2.cvtColor(actual_map_image, cv2.COLOR_BGR2GRAY)
h, w = actual_map_image.shape[:2]
flood_mask = np.zeros((h+2, w+2), dtype=np.uint8)
connectivity = 8
flood_fill_flags = (connectivity | cv2.FLOODFILL_FIXED_RANGE | cv2.FLOODFILL_MASK_ONLY | 255 << 8)
# Copy the image.
im_floodfill = actual_map_image.copy()
# Floodfill from point inside arena, not inside a black dot
cv2.floodFill(im_floodfill, flood_mask, (h/2 + 20, w/2 + 20), 255, None, None, flood_fill_flags)
borders = []
for i in range(len(actual_map_image)):
borders.append([B-A for A,B in zip(actual_map_image[i], flood_mask[i])])
borders = np.asarray(borders)
borders = cv2.bitwise_not(borders)
# Display images.
cv2.imshow("Original Image", cv2.resize(actual_map_image, (500, 500)))
cv2.imshow("Floodfilled Image", cv2.resize(flood_mask, (500, 500)))
cv2.imshow("Borders", cv2.resize(borders, (500, 500)))
cv2.waitKey(0)
I get this:
However, I feel like this is the wrong way of getting the borders, and they are incomplete.
I think the easiest, and fastest, way to do this is to flood-fill the arena with mid-grey. Then extract just the grey pixels and find their edges. That looks like this, but bear in mind more than half the lines are comments and debug statements :-)
#!/usr/bin/env python3
import cv2
# Load image as greyscale to use 1/3 of the memory and processing time
im = cv2.imread('arena.png', cv2.IMREAD_GRAYSCALE)
# Floodfill arena area with value 128, i.e. mid-grey
floodval = 128
cv2.floodFill(im, None, (150,370), floodval)
# DEBUG cv2.imwrite('result-1.png', im)
# Extract filled area alone
arena = ((im==floodval) * 255).astype(np.uint8)
# DEBUG cv2.imwrite('result-2.png', arena)
# Find edges and save
edges = cv2.Canny(arena,100,200)
# DEBUG cv2.imwrite('result-3.png',edges)
Here are the 3 steps of debug output showing you the sequence of processing:
result-1.png looks like this:
result-2.png looks like this:
result-3.png looks like this:
By the way, you don't have to write any Python code to do this, as you can just do it in the Terminal with ImageMagick which is included in most Linux distros and is available for macOS and Windows. The method used here corresponds exactly to the method I used in Python above:
magick arena.png -colorspace gray \
-fill gray -draw "color 370,150 floodfill" \
-fill white +opaque gray -canny 0x1+10%+30% result.png
How about dilating and xor
kernel = np.ones((3,3), np.uint8)
dilated = cv2.dilate(actual_map_image, kernel, iterations = 1)
borders = cv2.bitwise_xor(dilated, actual_map_image)
That will give you only the borders, I'm not clear if you want the circle borders only or also the interior borders, you should be able to remove borders you don't want based on size.
You can remove the exterior border with a size threshold, define a function like this:
def size_threshold(bw, minimum, maximum):
retval, labels, stats, centroids = cv.connectedComponentsWithStats(bw)
for val in np.where((stats[:, 4] < minimum) + (stats[:, 4] > maximum))[0]:
labels[labels==val] = 0
return (labels > 0).astype(np.uint8) * 255
result = size_threshold(borders, 0, 500)
Replace 500 with the a number larger than borders you want to keep and smaller than the border you want to lose.
I had to create my own Flood Fill implementation to get what I wanted. I based myself on this one.
def fill(data, start_coords, fill_value, border_value, connectivity=8):
"""
Flood fill algorithm
Parameters
----------
data : (M, N) ndarray of uint8 type
Image with flood to be filled. Modified inplace.
start_coords : tuple
Length-2 tuple of ints defining (row, col) start coordinates.
fill_value : int
Value the flooded area will take after the fill.
border_value: int
Value of the color to paint the borders of the filled area with.
connectivity: 4 or 8
Connectivity which we use for the flood fill algorithm (4-way or 8-way).
Returns
-------
filled_data: ndarray
The data with the filled area.
borders: ndarray
The borders of the filled area painted with border_value color.
"""
assert connectivity in [4,8]
filled_data = data.copy()
xsize, ysize = filled_data.shape
orig_value = filled_data[start_coords[0], start_coords[1]]
stack = set(((start_coords[0], start_coords[1]),))
if fill_value == orig_value:
raise ValueError("Filling region with same value already present is unsupported. Did you already fill this region?")
border_points = []
while stack:
x, y = stack.pop()
if filled_data[x, y] == orig_value:
filled_data[x, y] = fill_value
if x > 0:
stack.add((x - 1, y))
if x < (xsize - 1):
stack.add((x + 1, y))
if y > 0:
stack.add((x, y - 1))
if y < (ysize - 1):
stack.add((x, y + 1))
if connectivity == 8:
if x > 0 and y > 0:
stack.add((x - 1, y - 1))
if x > 0 and y < (ysize - 1):
stack.add((x - 1, y + 1))
if x < (xsize - 1) and y > 0:
stack.add((x + 1, y - 1))
if x < (xsize - 1) and y < (ysize - 1):
stack.add((x + 1, y + 1))
else:
if filled_data[x, y] != fill_value:
border_points.append([x,y])
# Fill all image with white
borders = filled_data.copy()
borders.fill(255)
# Paint borders
for x,y in border_points:
borders[x, y] = border_value
return filled_data, borders
The only thing I did was adding the else condition. If the point does not have a value equal to orig_value or fill_value, then it is a border, so I append it to a list that contains the points of all borders. Then I only paint the borders.
I was able to get the following images with this code:
# Resize for test purposes
actual_map_image = cv2.resize(actual_map_image, (500, 500))
actual_map_image = cv2.cvtColor(actual_map_image, cv2.COLOR_BGR2GRAY)
h, w = actual_map_image.shape[:2]
filled_data, borders = fill(actual_map_image, [h/2 + 20, w/2 + 20], 127, 0, connectivity=8)
cv2.imshow("Original Image", actual_map_image)
cv2.imshow("Filled Image", filled_data)
cv2.imshow("Borders", borders)
The one on the right was what I was aiming for. Thank you all!

How to modify the pixel value of each pixel conditionally using PIL Image ONLY

I want to reduce the pixel value by 100 for all pixels (all r,g,b)
then if update the pixel values to 255 (all r,g,b) where the r=g=b and r > 127
I have tried using CV2 and numpy it works fine, however i am asked to do it using pure PIL Image only.
The code in CV2/numpy is
def getCorrectedImage(im):
print type(im), im.shape
multiplier = np.ones(im.shape, dtype="uint8") * 100
outImage = cv2.subtract(im, multiplier)
height, width, channel = outImage.shape
for x in range(0, height):
for y in range(0, width):
b, g, r = outImage[x, y]
if b > 128 and g > 128 and r > 128:
outImage[x, y] = (255, 255, 255)
return outImage
I want similar code using pure PIL Image, I am not allowed to import CV2 or numpy
Something like that ?
def correct(pImg):
vImg = pImg
width, height = vImg.size
for x in range(width):
for y in range(height):
pixel = (pix - 100 for pix in vImg.getpixel((x, y)))
if (pixel[0] > 127 && pixel.count(pixel[0]) == 3):
pixel = (255, 255, 255)
vImg.putpixel((x,y),pixel)
return vImg
#IQbrod 's answer (after rectification) may work for the immediate problem, but is quite inefficient in the long run.
def getCorrectedImage(img):
data = list(img.getdata())
new_data = [(255, 255, 255) if x[0]== x[1] and x[1] == x[2] and x[0] > 127 else (x[0]-100, x[1]-100, x[2]-100) for x in data]
img.putdata(new_data)
return img
The above code, takes in an image object (created via Image.open) and then obtains it's pixel map using img.getdata() and stores it in a variable (data) of type list. Then uses list comprehension for modifying pixel values, guided by a condition. And in the end returns the modified image object.

How to remove unwanted parts in an image and then create a new small image using Python?

I want to remove the dark(black strips) and also the white curves in the image, and then align the remained parts connected in a new small-sized image, making the colored parts looks continuously. I hope can get some suggestions for solutions.
I have tried to use PIL to read the image.
I don't know how to set the right threshold and resize the image
I'm not an expert at all in image processing, but let me know if this is enough for you.
Looking at the brightness (sum of the RGB values) distribution, maybe one option is to just filter pixel based on their value:
It looks like the dark parts have a brightness below 100 (or something like that). I filtered it this way:
from PIL import Image
import numpy as np
def filter_image(img,threshold=100):
data = np.array(img.convert('RGB'))
brightness = np.sum(data,axis=2)
filtered_img = data.copy()*0
for i in range(data.shape[0]):
k = 0 # k index the columns that are bright enough
for j in range(data.shape[1]):
if brightness[i,j] > threshold:
filtered_img[i,k,:] = data[i,j,:]
k += 1 # we increment only if it's bright enough
# End of column iterator. The right side of the image is black
return Image.fromarray(filtered_img)
img = Image.open("test.png")
filtered = filter_image(img)
filtered.show()
I get the following result. I'm sure experts can do much better, but it's a start:
The following is only looking for black pixels, as can be seen by the first image, many of the pixels you want out are not black. You will need to find a way to scale up what you will take out.
Also, research will need to be done on collapsing an image, as can be seen by my collapse. Although, this image collapse may work if you are able to get rid of everything but the reddish colors. Or you can reduce by width, which is what the third picture shows.
from PIL import Image, ImageDraw
def main():
picture = Image.open('/Volumes/Flashdrive/Stack_OverFlow/imageprocessing.png', 'r')
# pix_val = list(im.getdata())
# print(pix_val)
# https://code-maven.com/create-images-with-python-pil-pillowimg = Image.new('RGB', (100, 30), color = (73, 109, 137))
blackcount = 0
pix = picture.convert('RGB') # https://stackoverflow.com/questions/11064786/get-pixels-rgb-using-pil
width, height = picture.size
img = Image.new('RGB', (width, height), color=(73, 109, 137))
newpic = []
for i in range(width):
newpictemp = []
for j in range(height):
# https://stackoverflow.com/questions/13167269/changing-pixel-color-python
r, g, b = pix.getpixel((i, j))
if r == 0 and g == 0 and b == 0:
blackcount += 1
else:
img.putpixel((i, j), (r, g, b))
newpictemp.append((r, g, b))
newpic.append(newpictemp)
img.save('pil_text.png')
newheight = int(((width * height) - blackcount) / width)
print(newheight)
img2 = Image.new('RGB', (width, newheight), color=(73, 109, 137))
for i in range(width):
for j in range(newheight):
try:
z = newpic[i][j]
img2.putpixel((i, j), newpic[i][j])
except:
continue
img2.save('pil_text2.png')
if __name__ == "__main__":
main()
No black pixels on left, removed black pixels on right, remove and resize by width (height resize shown in code)

Categories

Resources