Python + OpenCV, change brightness/darkness outside a sliding window? - python

I'm working with Python and OpenCV and I'm a newbie in both.
For my project, I need to move a sliding window over a picture; for each position of the window the area outside the window must be shown darker than the area inside the window.
This is the part of my code that takes care of the picture and window visualization (the valid positions for the sliding window are calculated somewhere else)
for (x, y, window) in valid_positions:
if window.shape[0] != winH or window.shape[1] != winW:
continue
# Put here stuff to process the window content
# i.e apply a classifier
clone = image.copy()
cv2.rectangle(clone, (x, y), (x + winW, y + winH), (0, 255, 0), 2)
cv2.imshow("Window", clone)
cv2.waitKey(1)
time.sleep(0.025)
The window is created and it slides on the valid positions, so that part works well. But I have absolutely no idea on how to make the picture outside the window appear darker.
Any suggestions?
Thanks in advance.
EDIT: i forgot to add an important detail: my input images are always in black and white (not even greyscale, just black and white pixels). Maybe this makes it easier to alter the brightness/darkness?

In general, you can preserve the content inside the window and lower the intensity of the entire image. Then replace the area inside the window with original content. That trick should work. This part of the code may look like
clone = image.copy()
windowArea = clone[y:y + winH, x:x + winW].copy()
clone = np.floor(clone * 0.5).astype('uint8') # 0.5 can be adjusted
clone[y:y + winH, x:x + winW] = windowArea
cv2.rectangle(clone, (x, y), (x + winW, y + winH), (0, 255, 0), 2)

Related

How to resize an image keeping the thickness of its border?

I'm making a GUI toolkit for the Python Arcade library, but I am stuck on a problem. I want the user to be able to customize sizes for the GUI widgets and graphics in pixels (width, height). But currently, the graphics are images. I have the images, but I want the user to be able to customize their sizing.
One of the images is shown below. Instead of using PIL to just stretch the width and height of the image, I need something else. Just stretching the width and height will make the border look too thick.
Is there an easy way to cut certain parts of the image to enable easy use for extending it? Borders would look like this. They would be split to extend the image. Some of the parts can be stretched, but some can not.
Your example seems to use a simple style, so a simplified solution could be used for it as well.
from PIL import Image
def resizeImage(im, corner, new_size):
'''
corner_size and new_size are 2-element tuples of xy sizes for the corner size and target size.
'''
# Get corners from image
tl = im.crop(0, 0, corner[0], corner[1])
tr = im.crop(im.size[0] - corner[0], 0, size[0], corner[1])
bl = im.crop(0, im.size[1] - corner[1], corner[0], size[1])
br = im.crop(im.size[0] - corner[0], im.size[1] - corner[1], size[0], size[1])
# Get 1-pixel slices of midsections, then scale them up as needed
h_slice = im.crop(corner[0] + 1, 0, corner[0] + 2, im.size[1])
h_slice = h_slice.resize((new_size[0] - 2 * corner[0], im.size[1]))
v_slice = im.crop(0, corner[1] + 1, im.size[0], corner[1] + 2)
v_slice = v_slice.resize((im.size[0], new_size[1] - 2 * corner[1]))
# create new image
new_im = Image.new('RGBA', new_size)
# paste on segments and corners
new_im.paste(tl, (0, 0))
new_im.paste(tr, (new_size[0] - corner[0], 0))
new_im.paste(tl, (0, new_size[1] - corner[1]))
new_im.paste(tl, (new_size[0] - corner[0], new_size[1] - corner[1]))
return im
This answer assumes that your borders are completely homogenous, in that there's no difference between any slice of the border (no patterns/textures).
If you do want to account for this, you can check out RenPy's approach to the problem. I'd track down the source code too, but the solution I proposed is a minimal solution for your specific example with a simple GUI style.
(Note that I have not run this code, so there may be a 1-pixel offset somewhere that I could have missed.)
It seems to be no easy way for resizing ( liquid resizing doesn't work here ) except (as suggested in the question with the second image) dividing the image using PIL crop() into nine (9) sub-images and resize them separately (except the corner sub-images, which won't become resized). The resized parts are then put together in a new image with the requested new size by pasting them using PIL paste() onto it. The borders are stretched only along their length and not along their thickness. Here how it looks like if the original image becomes resized with the further down provided resizeExceptBorder() function:
Original image (200 x 30)
new_img_1 = resizeExceptBorder(PIL_image,(300,90),(5,5,5,5))
Resized image (300 x 90)
new_img_2 = resizeExceptBorder(PIL_image,(400,150),(5,5,5,5))
Resized (400 x 150)
And here the code of the function I have put together for this purpose:
def resizeExceptBorder(PIL_image, newSize, borderWidths):
"""
newSize = (new_width, new_height)
borderWidths = (leftWidth, rightWidth, topWidth, bottomWidth)"""
pl_img = PIL_image
sXr, sYr = newSize # ( 800, 120 ) # resized size X, Y
lWx, rWx , tWy, bWy = borderWidths
sX, sY = pl_img.size
sXi, sYi = sXr-(lWx+rWx), sYr-(tWy+bWy)
pl_lft_top = pl_img.crop(( 0, 0, lWx, tWy))
pl_rgt_top = pl_img.crop((sX-rWx, 0, sX, tWy))
pl_lft_btm = pl_img.crop(( 0, sY-bWy, lWx, sY))
pl_rgt_btm = pl_img.crop((sX-rWx, sY-bWy, sX, sY))
# ---
pl_lft_lft = pl_img.crop(( 0, tWy, lWx,sY-bWy)).resize((lWx ,sYi))
pl_rgt_rgt = pl_img.crop((sX-rWx, tWy, sX,sY-bWy)).resize((rWx ,sYi))
pl_top_top = pl_img.crop(( lWx, 0, sX-rWx, tWy)).resize((sXi ,tWy))
pl_btm_btm = pl_img.crop(( lWx, sY-bWy, sX-rWx, sY)).resize((sXi ,bWy))
# ---
pl_mid_mid = pl_img.crop(( lWx, tWy, sX-rWx,sY-bWy)).resize((sXi,sYi))
# -------
pl_new=Image.new(pl_img.mode, (sXr, sYr))
# ---
pl_new.paste(pl_mid_mid, ( lWx, tWy))
# ---
pl_new.paste(pl_top_top, ( lWx, 0))
pl_new.paste(pl_btm_btm, ( lWx,sYr-bWy))
pl_new.paste(pl_lft_lft, ( 0, tWy))
pl_new.paste(pl_rgt_rgt, (sXr-rWx, tWy))
# ---
pl_new.paste(pl_lft_top, ( 0, 0))
pl_new.paste(pl_rgt_top, (sXr-rWx, 0))
pl_new.paste(pl_lft_btm, ( 0,sYr-bWy))
pl_new.paste(pl_rgt_btm, (sXr-rWx,sYr-bWy))
# ---
return pl_new
#:def

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).

paste an image onto section of an image in a way it will expand inside the given area

for now i have created an interface with two large labels using pyqt5 and i have used those labels to show images in order to get animation like illustration to my project. bellow images shows the pyqt interface holding the two images in two labels.
what i want to do now is to save these instances as images and later create an animation using them as frames. for now i have am using the same function to create other the images. the two images are made different due to the given input list of puzzle pieces. the size of the second image is large due to the expanding features of the pyqt.
following code is the function i used to create the images. it might not make complete sense. but its not that important for this question.
def make_img(results, puzzle):
final = Image.new('RGBA', (165 * COL_NUM, 165 * ROW_NUM))
# border = 100
keys = list(results.keys())
for i in keys:
piece = puzzle[results[i]]
image = piece.img
corners = piece.corners
pos = piece.pos
# print("shape :",np.asarray(image).shape)
w = np.asarray(image).shape[0]
image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2RGBA)
cv2.putText(image, str(results[i]), (int(w / 2), int(w / 2)), cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)
image[np.all(image == [0, 0, 0, 255], axis=2)] = [0, 0, 0, 0]
corner0 = corners[0]
x = pos[0] - corner0[0]
y = pos[1] - corner0[1]
img = Image.fromarray(np.uint8(image))
final.paste(img, (x, y), img)
imageBox = final.getbbox()
cropped = final.crop(imageBox)
return cropped
i want to save a final frame by putting together both of these parts of them images.
is there a way to create a large empty image. and past those two part (maybe will have some other parts too) in to predefined section with expiation and centering features to it.

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)

PySide: Separating a spritesheet / Separating an image into contiguous regions of color

I'm working on a program in which I need to separate spritesheets, or in other words, separate an image into contiguous regions of color.
I've never done any image processing before, so I'm wondering how I would go about this. What would I do after I test for pixel color? What's the best way to determine which pixel goes with each sprite?
All the input images have uniform backgrounds, and an alpha channel different from that of the background counts as color. The order of the output images needs to be left-right, up-down. My project is written in PySide, so I'm hoping to use it for this task too, but I could import more libraries if necessary.
Thanks your replies!
P.S.:
I'm not sure if the PySide tag is appropriate or not, since I'm using PySide, but the question doesn't involve the GUI aspects of it. If a mod feels it doesn't belong, feel free to remove it.
For example, I have a spritesheet that looks like this:
I want to separate it into these:
That sounds like something that should be implemented in anything that deals with sprites, but here we will implement our own sprite-spliter.
The first thing we need here is to extract the individual objects. In this situation, it is only a matter of deciding whether a pixel is a background one or not. If we assume the point at origin is a background pixel, then we are done:
from PIL import Image
def sprite_mask(img, bg_point=(0, 0)):
width, height = img.size
im = img.load()
bg = im[bg_point]
mask_img = Image.new('L', img.size)
mask = mask_img.load()
for x in xrange(width):
for y in xrange(height):
if im[x, y] != bg:
mask[x, y] = 255
return mask_img, bg
If you save the mask image created above and open it, here is what you would see on it (I added a rectangle inside your empty window):
With the image above, the next thing we need is to fill its holes if we want to join sprites that are inside others (like the rectangle added, see figure above). This is another simple rule: if a point cannot be reached from the point at [0, 0], then it is a hole and it must be filled. All that is left is then separating each sprite in individual images. This is done by connected component labeling. For each component we get its axis-aligned bounding box in order to define the dimensions of the piece, and then we copy from the original image the points that belong to a given component. To keep it short, the following code uses scipy for these tasks:
import sys
import numpy
from scipy.ndimage import label, morphology
def split_sprite(img, mask, bg, join_interior=True, basename='sprite_%d.png'):
im = img.load()
m = numpy.array(mask, dtype=numpy.uint8)
if join_interior:
m = morphology.binary_fill_holes(m)
lbl, ncc = label(m, numpy.ones((3, 3)))
for i in xrange(1, ncc + 1):
px, py = numpy.nonzero(lbl == i)
xmin, xmax, ymin, ymax = px.min(), px.max(), py.min(), py.max()
sprite = Image.new(img.mode, (ymax - ymin + 1, xmax - xmin + 1), bg)
sp = sprite.load()
for x, y in zip(px, py):
x, y = int(x), int(y)
sp[y - int(ymin), x - int(xmin)] = im[y, x]
name = basename % i
sprite.save(name)
print "Wrote %s" % name
sprite = Image.open(sys.argv[1])
mask, bg = sprite_mask(sprite)
split_sprite(sprite, mask, bg)
Now you have all the pieces (sprite_1.png, sprite_2.png, ..., sprite_8.png) exactly as you included in the question.

Categories