I am using cv2.rectangles to attempt to draw a grid on an np.zeros window. I am looking for a way to use a for or while loop to automattically repeat the drawing of these lines (long thick rectangles) until the horizontal lines meet the bottom of the window and vertical lines the width.
I'd like for the algorithm to populate with rectangles of unchanging size/space between them, to the edges of any window size that is input. So the window would change size but the size of each grid line/square would not.
current code.
import cv2
import numpy as np
#create np.zeros window
frame = np.zeros((600,600, 3), np.uint8)
width = frame.shape[0]
height = frame.shape[1]
#starting points for vertical line, #earlier while loop wouldn't let me use'<' with tuples
vertp1y = 25
vertp1x = 0
vertp2y = 35
vertp2x = height
#starting points for horizontal lines
horizp1y = 0
horizp1x = 25
horizp2y = width
horizp2x = 35
#pt1 and pt2 parameters set this way to attempting using as variables in later while loop
vert_line=cv2.rectangle(frame, (vertp1y, vertp1x), (vertp2y, vertp2x), (0,225,0), -1)
horiz_line=cv2.rectangle(frame, (horizp1y, horizp1x), (horizp2y, horizp2x), (0,225,0), -1)
while vertp2y < width:
vertp1y = vertp1y + 25
vertp2y = vertp2y + 25
if vertp2y > width:
break
cv2.imshow('frame', frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
when I run this I get no errors but the window with the two lines (rectangles) are returned unchanged.
I also attempted to use warpAffine translation using vert_line as the src, same issue, unchanged window returned.
M = np.float32([[-1,0,25], [0,1,0]])
vert_line_2 = cv2.warpAffine(vert_line, M, (frame.shape[1], frame.shape[0]))
I think the easiest option would be to draw the lines and columns instead of rectangles. To do this, you could try something like this:
import matplotlib.pyplot as plt
import numpy as np
class BoxesDrawer:
def __init__(self, box_size: tuple, border_size: int, color: tuple = (0, 255, 0)):
self.box_size = box_size
self.half_border_size = int(border_size // 2)
self.color = color
def draw_on(self, frame: np.asarray) -> np.asarray:
self._draw_row(frame)
self._draw_columns(frame)
return frame
def _draw_row(self, frame: np.asarray) -> np.asarray:
row_size = self.box_size[0]
index = 0
while True:
index += row_size
if index > frame.shape[0]:
break
frame[index - self.half_border_size:index + self.half_border_size, :, :] = self.color
return frame
def _draw_columns(self, frame: np.asarray) -> np.asarray:
column_size = self.box_size[1]
index = 0
while True:
index += column_size
if index > frame.shape[0]:
break
frame[:, index - self.half_border_size:index + self.half_border_size, :] = self.color
return frame
if __name__ == '__main__':
frame = np.zeros((224, 224, 3))
drawer = BoxesDrawer(box_size=(20, 20), border_size=3, color=(0, 255, 0))
drawer.draw_on(frame)
plt.figure()
plt.imshow(frame)
plt.show()
Not really a good looking code, though it should work for your use case. I'm 100% sure this could be optimized and you can even modify it to better fit your use case, but the baseline is here.
Related
I am attempting to use a grid of rectangles over my live webcam video with dynamically changing transparencies across the screen. The test I am doing here does not hold the dynamic aspect, but the rectangles should be different transparencies according to their normalized value in the rng array of values. All of the rectangles in the grid are resulting as the same transparency.
#===========================================================================================================================
# Import Necessary Libraries
import cv2 as cv
import numpy as np
from sklearn import preprocessing
import math
import itertools
#===========================================================================================================================
# Define a Video Capture
vid = cv.VideoCapture(0)
#===========================================================================================================================
# Initialize
height = int(input('Define Number of Rows: '))
width = int(input('Define Number of Columns: '))
hxw = height * width
#===========================================================================================================================
# Create a Random Number Array
randnums= np.random.randint(1,30000,(hxw))
normran = (randnums - randnums.min())/((randnums.max() - randnums.min()))
print(normran)
#===========================================================================================================================
# Create a Color Map
def colorgrid(img, normran):
h, w, _ = img.shape
dy = h / height
dx = w / width
g = np.linspace(0, w-dx, width)
b = np.linspace(g[1], w, width)
t = np.linspace(0, h-dy, height)
k = np.linspace(t[1], h, height)
overlay = img.copy()
thickness = 2
color3 = (0, 0, 0)
color2 = (0, 0, 255)
thickness1 = -1
i = -1
for spy, spay in itertools.product(t, k):
for spx, spax in itertools.product(g, b):
i = i + 1
alpha = normran[i]
spx = int(round(spx))
spax = int(round(spax))
spy = int(round(spy))
spay = int(round(spay))
startx = (spx, spy)
stopx = (spax, spay)
print(alpha)
print(i)
cv.rectangle(overlay, startx, stopx, color2, thickness1)
imgnew = cv.addWeighted(overlay, normran[i], img, 1, 0)
cv.rectangle(img, startx, stopx, color3, thickness)
if (i) == (hxw-1):
i = -1
break
return imgnew
#===========================================================================================================================
# Use the Camera
while(True):
# the 'q' button is set as the
# quitting button you may use any
# desired button of your choice
if cv.waitKey(1) & 0xFF == ord('q'):
break
# Capture the video frame
# by frame
ret, frame = vid.read()
frame = cv.flip(frame,1)
clr = colorgrid(frame, normran)
# Display the resulting frame
cv.imshow('Test', clr)
# After the loop release the cap object
vid.release()
# Destroy all the windows
cv.destroyAllWindows()
I tried to use a for loop that increments for the number of grid spaces defined and will end when the number of spaces are up. This is successful, but it seems as the image only results in the alpha value associated with normran[end]. The output of the input of 3 rows and 3 columns is:
0.90409176345848
0
0.07855800279531544
1
0.4564557328064003
2
0.9938310280013495
3
0.0
4
0.16005590630873778
5
1.0
6
0.9541182707600366
7
0.1760566774302376
8
0.90409176345848
0
0.07855800279531544
1
0.4564557328064003
2
0.9938310280013495
3
0.0
4
0.16005590630873778
5
1.0
6
0.9541182707600366
7
0.1760566774302376
8
^^ this repeats infinitely
An image of the live webcam
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)
Is possible to implement this image filtering process in numpy array ? I need to check if the pixel in the previous column and previous row is differente of the current pixel.
width, height = orig_bin.size
pixels = orig_bin.load()
delta = 50
begin = 10
min_w = 30
max_w = 260
min_h = 10
max_h = 40
w_range = range(begin, width - min_w - delta)
h_range = range(begin, height - min_h - delta)
is_changing = False
for x in w_range:
for y in h_range:
change_pixel = False
current_pixel = pixels[x,y]
if current_pixel != pixels[x, y+1]:
change_pixel = True
if current_pixel != pixels[x+1, y]:
change_pixel = True
if change_pixel:
pixels[x,y] = (0,0,0)
else:
pixels[x,y] = (255,255,255)
Best regards,
Emilio
Here's one approach. Take an example image:
You didn't say where your orig_bin came from, so I've used scipy.misc.imread:
from scipy.misc import imread, imsave
img = imread('input.png')
First, create a mask for pixels that are different from the pixel above (this uses an idea from Bi Rico's answer):
up = (img[1:,1:] != img[:-1,1:]).any(axis=2)
Note that imread loads images in row-major order, so the first NumPy axis is the vertical axis. See "Multidimensional Array Indexing Order Issues" for an explanation.
Similarly, create a mask for pixels that are different from the pixel to the left:
left = (img[1:,1:] != img[1:,:-1]).any(axis=2)
Combine these to get a mask for pixels that are different to either the pixel above or left:
mask = numpy.zeros(img.shape[:2], dtype=bool)
mask[1:,1:] = left | up
Create a black output image of the right size; then set the mask to white:
output = numpy.zeros(img.shape)
output[mask] = (255, 255, 255)
imsave('output.png', output)
And here's the result:
or if you want the colours to be the other way round, invert the mask:
output[~mask] = (255, 255, 255)
You want something like:
change = (pixels[1:, 1:] != pixels[1:, :-1]) | (pixels[1:, 1:] != pixels[:-1, 1:])
This will be binary (True, False), you'll need to multiply it by 255 if you want your result to be 0/255. You also might need to run and any on the last dimension if you array is (x, y, 3).
There are other ways you could re-cast this problem, for example a convolution with [1, -1] might get you most of the way there.
I want to rotate a gray "test" image and paste it onto a blue background image. Now I just can remove the black color after rotate my gray "test" image, but their is now a white color section. How can I use Python to change the "white" color section to blue?
Here is my code, can someone help me? I'd appreciate it.
dst_im = Image.new("RGBA", (196,283), "blue" )
im = src_im.convert('RGBA')
rot = im.rotate( angle, expand=1 ).resize(size)
f = Image.new( 'RGBA', rot.size, (255,)*4 )
im2 = Image.composite( rot, f, rot )
im2.convert(src_im.mode)
im2_width, im2_height = im2.size
cut_box = (0, 0, im2_width, im2_height )
paste_box = ( left, top, im2_width+left, im2_height+top )
region = im2.crop( cut_box )
dst_im.paste( region, paste_box )
dst_im.save("test.gif")
I have the impression that your code could be simplified as follows:
from PIL import Image
src_im = Image.open("winter3.jpg")
angle = 45
size = 100, 100
dst_im = Image.new("RGBA", (196,283), "blue" )
im = src_im.convert('RGBA')
rot = im.rotate( angle, expand=1 ).resize(size)
dst_im.paste( rot, (50, 50), rot )
dst_im.save("test.png")
This gives the following result:
Another answer using PIL is clearly more succinct. I had a similar problem and had the image in an ndarray. Yipes, mine came out way more complicated than user1202136. I'm posting it only because it demonstrates another solution using numpy and array stacking, but user1202136's solution is much better.
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
def rgba(rgb_img, alpha):
'''
' takes an rgb ndarray r x c x 3 of dtype=uint8
' and adds an alpha 0-255 to each pixel
'''
rows = len(rgb_img) # get image dimensions
columns = len(rgb_img[0])
rgb_flat = rgb_img.reshape([rows * columns, 3]) # list of rgb pixels
a = np.zeros([rows*columns, 1], dtype=np.uint8) # alpha for each pixel
a.fill(alpha)
rgba = np.column_stack([rgb_flat, a]) # place 4th column
return rgba.reshape([rows, columns, 4]) # reform into r x c x 4
def pad_with_transparent_pixels(rgba_img):
'''
' takes an rgba image r x c
' and places within a buffer of [ 0 0 0 0] to become square,
' with sides = diagonal of img
'''
rows = len(rgba_img) # get image dimensions
columns = len(rgba_img[0])
diag = (rows**2 + columns**2)**0.5
diag = int(diag) + 1
top_pad_height = (diag-rows)/2 + 1
left_pad_width = (diag-columns)/2 + 1
top_pad = np.zeros([top_pad_height, diag, 4], dtype=np.uint8)
left_pad = np.zeros([rows, left_pad_width, 4], dtype=np.uint8)
right_pad = np.zeros([rows,
# assures total width of top_pad for row_stack:
diag - left_pad_width - columns,
4 ],
dtype=np.uint8)
center = np.column_stack([left_pad, rgba_img, right_pad])
return np.row_stack([top_pad, center, top_pad])
def clean_rotate(rgba_img,angle):
rows = len(rgba_img)
columns = len(rgba_img[0])
diag = (rows**2 + columns**2)**.5
diag = int(diag)
pad_img = pad_with_transparent_pixels(rgba_img)
rot_img = scipy.ndimage.rotate(pad_img, angle)
rot_img_rows = len(rot_img)
rot_img_columns = len(rot_img[0])
crop_side = max(1,(rot_img_columns - diag) / 2) #max to avoid splicing [:0]
crop_top = max(1,(rot_img_rows - diag) / 2)
print diag, crop_side, crop_top
return rot_img[crop_top:-crop_top,crop_side:-crop_side]
img = plt.imread('C:\\Users\\bbrown\\Desktop\\Maurine.jpg') # read in a jpg
figure, axes = plt.subplots(1, 2) # create 1x2 grid of axes
axes[0].imshow(img) # place image on first axes
rgba_image = rgba(img, 255) # create an opaque rgba image
rot_img = clean_rotate(rgba_image,50)
#make a pattern of 10 images
for i in range(10):
rot_img = clean_rotate(rgba_image,5*i)
axes[1].imshow(rot_img)
plt.show()
I am trying to remove a certain color from my image however it's not working as well as I'd hoped. I tried to do the same thing as seen here Using PIL to make all white pixels transparent? however the image quality is a bit lossy so it leaves a little ghost of odd colored pixels around where what was removed. I tried doing something like change pixel if all three values are below 100 but because the image was poor quality the surrounding pixels weren't even black.
Does anyone know of a better way with PIL in Python to replace a color and anything surrounding it? This is probably the only sure fire way I can think of to remove the objects completely however I can't think of a way to do this.
The picture has a white background and text that is black. Let's just say I want to remove the text entirely from the image without leaving any artifacts behind.
Would really appreciate someone's help! Thanks
The best way to do it is to use the "color to alpha" algorithm used in Gimp to replace a color. It will work perfectly in your case. I reimplemented this algorithm using PIL for an open source python photo processor phatch. You can find the full implementation here. This a pure PIL implementation and it doesn't have other dependences. You can copy the function code and use it. Here is a sample using Gimp:
to
You can apply the color_to_alpha function on the image using black as the color. Then paste the image on a different background color to do the replacement.
By the way, this implementation uses the ImageMath module in PIL. It is much more efficient than accessing pixels using getdata.
EDIT: Here is the full code:
from PIL import Image, ImageMath
def difference1(source, color):
"""When source is bigger than color"""
return (source - color) / (255.0 - color)
def difference2(source, color):
"""When color is bigger than source"""
return (color - source) / color
def color_to_alpha(image, color=None):
image = image.convert('RGBA')
width, height = image.size
color = map(float, color)
img_bands = [band.convert("F") for band in image.split()]
# Find the maximum difference rate between source and color. I had to use two
# difference functions because ImageMath.eval only evaluates the expression
# once.
alpha = ImageMath.eval(
"""float(
max(
max(
max(
difference1(red_band, cred_band),
difference1(green_band, cgreen_band)
),
difference1(blue_band, cblue_band)
),
max(
max(
difference2(red_band, cred_band),
difference2(green_band, cgreen_band)
),
difference2(blue_band, cblue_band)
)
)
)""",
difference1=difference1,
difference2=difference2,
red_band = img_bands[0],
green_band = img_bands[1],
blue_band = img_bands[2],
cred_band = color[0],
cgreen_band = color[1],
cblue_band = color[2]
)
# Calculate the new image colors after the removal of the selected color
new_bands = [
ImageMath.eval(
"convert((image - color) / alpha + color, 'L')",
image = img_bands[i],
color = color[i],
alpha = alpha
)
for i in xrange(3)
]
# Add the new alpha band
new_bands.append(ImageMath.eval(
"convert(alpha_band * alpha, 'L')",
alpha = alpha,
alpha_band = img_bands[3]
))
return Image.merge('RGBA', new_bands)
image = color_to_alpha(image, (0, 0, 0, 255))
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image.convert('RGB'), mask=image)
Using numpy and PIL:
This loads the image into a numpy array of shape (W,H,3), where W is the
width and H is the height. The third axis of the array represents the 3 color
channels, R,G,B.
import Image
import numpy as np
orig_color = (255,255,255)
replacement_color = (0,0,0)
img = Image.open(filename).convert('RGB')
data = np.array(img)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.show()
Since orig_color is a tuple of length 3, and data has
shape (W,H,3), NumPy
broadcasts
orig_color to an array of shape (W,H,3) to perform the comparison data ==
orig_color. The result in a boolean array of shape (W,H,3).
(data == orig_color).all(axis = -1) is a boolean array of shape (W,H) which
is True wherever the RGB color in data is original_color.
#!/usr/bin/python
from PIL import Image
import sys
img = Image.open(sys.argv[1])
img = img.convert("RGBA")
pixdata = img.load()
# Clean the background noise, if color != white, then set to black.
# change with your color
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
You'll need to represent the image as a 2-dimensional array. This means either making a list of lists of pixels, or viewing the 1-dimensional array as a 2d one with some clever math. Then, for each pixel that is targeted, you'll need to find all surrounding pixels. You could do this with a python generator thus:
def targets(x,y):
yield (x,y) # Center
yield (x+1,y) # Left
yield (x-1,y) # Right
yield (x,y+1) # Above
yield (x,y-1) # Below
yield (x+1,y+1) # Above and to the right
yield (x+1,y-1) # Below and to the right
yield (x-1,y+1) # Above and to the left
yield (x-1,y-1) # Below and to the left
So, you would use it like this:
for x in range(width):
for y in range(height):
px = pixels[x][y]
if px[0] == 255 and px[1] == 255 and px[2] == 255:
for i,j in targets(x,y):
newpixels[i][j] = replacementColor
If the pixels are not easily identifiable e.g you say (r < 100 and g < 100 and b < 100) also doesn't match correctly the black region, it means you have lots of noise.
Best way would be to identify a region and fill it with color you want, you can identify the region manually or may be by edge detection e.g. http://bitecode.co.uk/2008/07/edge-detection-in-python/
or more sophisticated approach would be to use library like opencv (http://opencv.willowgarage.com/wiki/) to identify objects.
This is part of my code, the result would like:
source
target
import os
import struct
from PIL import Image
def changePNGColor(sourceFile, fromRgb, toRgb, deltaRank = 10):
fromRgb = fromRgb.replace('#', '')
toRgb = toRgb.replace('#', '')
fromColor = struct.unpack('BBB', bytes.fromhex(fromRgb))
toColor = struct.unpack('BBB', bytes.fromhex(toRgb))
img = Image.open(sourceFile)
img = img.convert("RGBA")
pixdata = img.load()
for x in range(0, img.size[0]):
for y in range(0, img.size[1]):
rdelta = pixdata[x, y][0] - fromColor[0]
gdelta = pixdata[x, y][0] - fromColor[0]
bdelta = pixdata[x, y][0] - fromColor[0]
if abs(rdelta) <= deltaRank and abs(gdelta) <= deltaRank and abs(bdelta) <= deltaRank:
pixdata[x, y] = (toColor[0] + rdelta, toColor[1] + gdelta, toColor[2] + bdelta, pixdata[x, y][3])
img.save(os.path.dirname(sourceFile) + os.sep + "changeColor" + os.path.splitext(sourceFile)[1])
if __name__ == '__main__':
changePNGColor("./ok_1.png", "#000000", "#ff0000")