Translate coordinates between coordinate systems opencv python - python

The corner coordinates of square_1 = (0, 0, 1920, 1080). I then define square_2 as a smaller ROI within square one using numpy slicing like so roi = square_1[y1:y2, x1:x2]. I then resize square_1 using square_resize = cv2.resize(square_1, (960, 540), interpolation = cv2.INTER_AREA) . However, now my ROI is no longer accurate. I have a tool which tells me the screen coords of the mouse pos, which is how I find the dimensions of the ROI, but I need a function that translates the ROI coordinates I find, given the coordinates of square_1, in terms of the coordinates of square_resize.
EDIT:
Solved using Panda50's answer. grab_screen() is my own custom function for getting screenshots. Here is my code if it helps anyone. It does not give 100% accurate coords but you can play around some and narrow it down.
from cv2 import cv2
import numpy as np
y1 = int(92 / 2)
y2 = int(491 / 2)
x1 = int(233 / 2)
x2 = int(858 / 2)
# grab screen and convert to RGB
screen = grab_screen(region = (0, 0, 1920, 1080))
screen = cv2.cvtColor(screen, cv2.COLOR_BGR2RGB)
# resize screen
screen = cv2.resize(screen, (960, 540), interpolation = cv2.INTER_AREA)
# define ROI
roi = screen[y1:y2, x1:x2].copy()
cv2.imshow('roi', roi)
cv2.waitKey()
cv2.destroyAllWindows()

In python, = associate one variable with another. By changing square_1 you'll also change roi .
You have to use :
roi = square_1[y1:y2, x1:x2].copy()

Related

How can I get python to show me the differance between 2 images and display them with Circles around

So I have started a program that takes two images, one that's the model image and the other that's an image with a change I want it to detect the differences and show me with circling the differences. I have come to an issue with finding the difference coordinates as my circle keeps ending up in the middle of the image.
This is the code I have:
import cv2 as cv
import numpy as np
from PIL import Image, ImageChops
#Ideal Image and The main Image
img2= cv.imread("ideal.jpg")
img1 = cv.imread("Actual.jpg")
#Verifys if there is or isnt a differance in the Image for the If statement
diff = cv.subtract(img2, img1)
results = not np.any(diff)
#Tells the User if there is a Differance within the 2 images with the model image and the image given
if results is True:
print("The Images are the same!")
else:
print("The images are differant")
#This is to make the image show the differance to circle
img_1=Image.open("Actual.jpg")
img_2=Image.open("ideal.jpg")
diff=ImageChops.difference(img_1,img_2)
diff.save("Differance.jpg")
#Reads the image Just saved
Differance = cv.imread("Differance.jpg", 0)
#Resize the Image to make it smaller
img1s = cv.resize(img1, (0, 0), fx=0.5, fy=0.5)
Differance = cv.resize(Differance, (0, 0), fx=0.5, fy=0.5)
# Find anything not black, i.e. The differance
nz = cv.findNonZero(Differance)
# Find top, bottom, left and right edge of the Differance
a = nz[:,0,0].min()
b = nz[:,0,0].max()
c = nz[:,0,1].min()
d = nz[:,0,1].max()
# Average top and bottom edges, left and right edges, to give centre
c0 = (a+b)/2
c1 = (c+d)/2
#The Center Coords
c3 = (int(c0),int(c1))
#Values for the below code so it doesnt look messy
radius = 50
color = (0, 0, 255)
thickness = 2
#This Places a Circle around the center of the differance
Finished = cv.circle(img1s, c3, radius, color, thickness)
#Saves the Final Image with the circle around it
cv.imwrite("Final.jpg", Finished)
And the Images attached 1
2
This code currently takes both images and blacks out the background leaving only the difference within the image then the program is meant to take the location of the difference and place a circle around the center of the main image that is the one with the difference on it.
Your main problem is JPG format which changes pixels to better compress image - and this creates differences in all area. If you display diff or difference then you should see many gray pixels
I hope you see pixels below ball
If you use PNG for original image (without ball) and later use this image to create image with ball and also save in PNG then code will works correctly.
My version without PIL.
Press any key to close window with image.
import cv2 as cv
import numpy as np
# load images
img1 = cv.imread("img1.png")
img2 = cv.imread("img2.png")
# calculate difference
diff = cv.subtract(img1, img2) # other order `(img2, img1)` gives worse result
# saves difference
cv.imwrite("difference.png", diff)
# show difference - press any key to close
cv.imshow('diff', diff)
cv.waitKey(0)
cv.destroyWindow('diff')
if not np.any(diff):
print("The images are the same!")
else:
print("The images are differant")
# resize images to make them smaller
#img1_resized = cv.resize(img1, (0, 0), fx=0.5, fy=0.5)
#diff_resized = cv.resize(diff, (0, 0), fx=0.5, fy=0.5)
img1_resized = img1
diff_resized = diff
# convert to grayscale (without saving and loading again)
diff_resized = cv.cvtColor(diff_resized, cv.COLOR_BGR2GRAY)
# find anything not black in differance
non_zero = cv.findNonZero(diff_resized)
#print(non_zero)
# find top, bottom, left and right edge of the differance
x_min = non_zero[:,0,0].min()
x_max = non_zero[:,0,0].max()
y_min = non_zero[:,0,1].min()
y_max = non_zero[:,0,1].max()
print('x:', x_min, x_max)
print('y:', y_min, y_max)
sizes = [x_max-x_min+1, y_max-y_min+1]
print('width :', sizes[0])
print('height:', sizes[1])
# center
center_x = (x_min + x_max) // 2
center_y = (y_min + y_max) // 2
center = (center_x, center_y)
print('center:', center)
# radius
radius = max(sizes) // 2
print('radius:', radius)
color = (0, 0, 255)
thickness = 2
# draw circle around the center of the differance
finished = cv.circle(img1_resized, center, radius, color, thickness)
# saves final image with circle
#cv.imwrite("final.png", finished)
# show final image - press any key to close
cv.imshow('finished', finished)
cv.waitKey(0)
cv.destroyWindow('finished')
img1.png
img2.png
difference.png
final.png
EDIT:
If you work with JPG then you can try to reduce noises
diff = cv.subtract(img1, img2)
diff_gray = cv.cvtColor(diff, cv.COLOR_BGR2GRAY)
diff_gray[diff_gray < 50] = 0
For different images you may need different values instead of 50.
You may also try thresholding
(_, diff_gray) = cv.threshold(diff_gray, 50, 0, cv.THRESH_TOZERO)
It may need also other functions like blur(), erode(), dilate(),
do not need PIL
take Differance image
threshold it
use findcontour to find regions
if contours finded then draw it
for cnt in contours:
out_image = cv2.drawContours(out_image, [cnt], 0, (255,0,0), -1)
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
out_image = cv2.circle(out_image,center,radius,(0,255,0),2)

Creating new image for given size containing cropped image

I am currently working on the below and am struggling to understand the best approach.
I've searched a lot but was not able to find answers that would match what I am trying to do
The problem:
Relocating an Object (e.g. Shoe) within the existing image (white background) to certain location (e.g. move up)
Inserting and positioning the Object (e.g. Shoe) at by the user specified location within a new background (still white) with by the user specified new height / width
How far I got:
I've managed identify the object within the picture using CV2, got the outer contours, added a little padding and cropped the object (see below). I am happy with cropping it that way as all my images have a one coloured background and I will keep the background in the same colour.
Where I am stuck:
My cropped Object and old image background / new background do not share the same shape, hence I am not able to overlay / concatenate / merge ...
Given both images are store as np arrays, I assume the answer will be to somehow place the Shoe crop np.array within the background np.array, however I have no clue how to do this.
Maybe there is an easier / different way to do this?
Would be very grateful to hear from anyone who can lead me into the right direction.
Code
#importing dependencies
import os
import numpy as np
import cv2
from matplotlib import pyplot as plt
# Config
path = '/Users/..../Shoes/'
img_list = os.listdir(path)
img_path = path + img_list[0]
#Outline
color = (0,255,0)
thickness = 3
padding = 10
# convert to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
# create a binary thresholded image
_, binary = cv2.threshold(gray, 225, 255, cv2.THRESH_BINARY_INV)
# find the contours from the thresholded image
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Identifying outer contours
x_axis = []
y_axis = []
for i in range(len(contours)):
for y in range (len(contours[i])):
x_axis.append(contours[i][y][0][0])
y_axis.append(contours[i][y][0][1])
min_x = min(x_axis) - padding
min_y = min(y_axis) - padding
max_x = max(x_axis) + padding
max_y = max(y_axis) + padding
# Defining start and endpoint of outline Rectangle based on identified outer corners + Padding
start_point = (min_x, min_y)
end_point = (max_x, max_y)
image_outline = cv2.rectangle(image, start_point, end_point, color, thickness)
plt.imshow(image_outline)
plt.show()
#Crop Image
crop_img = image[min_y:max_y, min_x:max_x]
print(crop_img.shape)
plt.imshow(crop_img)
plt.show()
I think I got to the solution, this centers the image for any new given background height/width
Still interested in quicker / cleaner ways
#Define the new height and width you want to have
new_height = 1200
new_width = 1200
#Check current hight and with of Cropped image
crop_height = crop_img.shape[0]
crop_width = crop_img.shape[1]
#calculate how much you need add to the sides and top - basically halft of the remaining height / with ... currently not working correctly for odd numbers
add_sides = int((new_width - crop_width)/2)
add_top_and_btm = int((new_height - crop_height)/2)
# Adding background to the sides
bg_sides = np.zeros(shape=[crop_height, add_sides, 3], dtype=np.uint8)
bg_sides2 = 255 * np.ones(shape=[crop_height, add_sides, 3], dtype=np.uint8)
new_crop_img = np.insert(crop_img, [1], bg_sides2, axis=1)
new_crop_img = np.insert(new_crop_img, [-1], bg_sides2, axis=1)
# Then adding Background to top and bottom
bg_top_and_btm = np.zeros(shape=[add_top_and_btm, new_width, 3],
dtype=np.uint8)
bg_top_and_btm2 = 255 * np.ones(shape=[add_top_and_btm, new_width, 3],
dtype=np.uint8)
new_crop_img = np.insert(new_crop_img, [1], bg_top_and_btm2, axis=0)
new_crop_img = np.insert(new_crop_img, [-1], bg_top_and_btm2, axis=0)
plt.imshow(new_crop_img)

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 do I draw text at an angle using python's PIL?

Using Python I want to be able to draw text at different angles using PIL.
For example, imagine you were drawing the number around the face of a clock. The number 3 would appear as expected whereas 12 would we drawn rotated counter-clockwise 90 degrees.
Therefore, I need to be able to draw many different strings at many different angles.
Draw text into a temporary blank image, rotate that, then paste that onto the original image. You could wrap up the steps in a function. Good luck figuring out the exact coordinates to use - my cold-fogged brain isn't up to it right now.
This demo writes yellow text on a slant over an image:
# Demo to add rotated text to an image using PIL
import Image
import ImageFont, ImageDraw, ImageOps
im=Image.open("stormy100.jpg")
f = ImageFont.load_default()
txt=Image.new('L', (500,50))
d = ImageDraw.Draw(txt)
d.text( (0, 0), "Someplace Near Boulder", font=f, fill=255)
w=txt.rotate(17.5, expand=1)
im.paste( ImageOps.colorize(w, (0,0,0), (255,255,84)), (242,60), w)
It's also usefull to know our text's size in pixels before we create an Image object. I used such code when drawing graphs. Then I got no problems e.g. with alignment of data labels (the image is exactly as big as the text).
(...)
img_main = Image.new("RGB", (200, 200))
font = ImageFont.load_default()
# Text to be rotated...
rotate_text = u'This text should be rotated.'
# Image for text to be rotated
img_txt = Image.new('L', font.getsize(rotate_text))
draw_txt = ImageDraw.Draw(img_txt)
draw_txt.text((0,0), rotate_text, font=font, fill=255)
t = img_value_axis.rotate(90, expand=1)
The rest of joining the two images together is already described on this page.
When you rotate by an "unregular" angle, you have to improve this code a little bit. It actually works for 90, 180, 270...
Here is a working version, inspired by the answer, but it works without opening or saving images.
The two images have colored background and alpha channel different from zero to show what's going on. Changing the two alpha channels from 92 to 0 will make them completely transparent.
from PIL import Image, ImageFont, ImageDraw
text = 'TEST'
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', 50)
width, height = font.getsize(text)
image1 = Image.new('RGBA', (200, 150), (0, 128, 0, 92))
draw1 = ImageDraw.Draw(image1)
draw1.text((0, 0), text=text, font=font, fill=(255, 128, 0))
image2 = Image.new('RGBA', (width, height), (0, 0, 128, 92))
draw2 = ImageDraw.Draw(image2)
draw2.text((0, 0), text=text, font=font, fill=(0, 255, 128))
image2 = image2.rotate(30, expand=1)
px, py = 10, 10
sx, sy = image2.size
image1.paste(image2, (px, py, px + sx, py + sy), image2)
image1.show()
The previous answers draw into a new image, rotate it, and draw it back into the source image. This leaves text artifacts. We don't want that.
Here is a version that instead crops the area of the source image that will be drawn onto, rotates it, draws into that, and rotates it back. This means that we draw onto the final surface immediately, without having to resort to masks.
def draw_text_90_into (text: str, into, at):
# Measure the text area
font = ImageFont.truetype (r'C:\Windows\Fonts\Arial.ttf', 16)
wi, hi = font.getsize (text)
# Copy the relevant area from the source image
img = into.crop ((at[0], at[1], at[0] + hi, at[1] + wi))
# Rotate it backwards
img = img.rotate (270, expand = 1)
# Print into the rotated area
d = ImageDraw.Draw (img)
d.text ((0, 0), text, font = font, fill = (0, 0, 0))
# Rotate it forward again
img = img.rotate (90, expand = 1)
# Insert it back into the source image
# Note that we don't need a mask
into.paste (img, at)
Supporting other angles, colors etc is trivial to add.
Here's a fuller example of watermarking diagonally. Handles arbitrary image ratios, sizes and text lengths by calculating the angle of the diagonal and font size.
from PIL import Image, ImageFont, ImageDraw
import math
# sample dimensions
pdf_width = 1000
pdf_height = 1500
#text_to_be_rotated = 'Harry Moreno'
text_to_be_rotated = 'Harry Moreno (morenoh149#gmail.com)'
message_length = len(text_to_be_rotated)
# load font (tweak ratio based on your particular font)
FONT_RATIO = 1.5
DIAGONAL_PERCENTAGE = .5
diagonal_length = int(math.sqrt((pdf_width**2) + (pdf_height**2)))
diagonal_to_use = diagonal_length * DIAGONAL_PERCENTAGE
font_size = int(diagonal_to_use / (message_length / FONT_RATIO))
font = ImageFont.truetype(r'./venv/lib/python3.7/site-packages/reportlab/fonts/Vera.ttf', font_size)
#font = ImageFont.load_default() # fallback
# target
image = Image.new('RGBA', (pdf_width, pdf_height), (0, 128, 0, 92))
# watermark
opacity = int(256 * .5)
mark_width, mark_height = font.getsize(text_to_be_rotated)
watermark = Image.new('RGBA', (mark_width, mark_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)
draw.text((0, 0), text=text_to_be_rotated, font=font, fill=(0, 0, 0, opacity))
angle = math.degrees(math.atan(pdf_height/pdf_width))
watermark = watermark.rotate(angle, expand=1)
# merge
wx, wy = watermark.size
px = int((pdf_width - wx)/2)
py = int((pdf_height - wy)/2)
image.paste(watermark, (px, py, px + wx, py + wy), watermark)
image.show()
Here it is in a colab https://colab.research.google.com/drive/1ERl7PiX6xKy5H9EEMulBKPgglF6euCNA?usp=sharing you should provide an example image to the colab.
I'm not saying this is going to be easy, or that this solution will necessarily be perfect for you, but look at the documentation here:
http://effbot.org/imagingbook/pil-index.htm
and especially pay attention to the Image, ImageDraw, and ImageFont modules.
Here's an example to help you out:
import Image
im = Image.new("RGB", (100, 100))
import ImageDraw
draw = ImageDraw.Draw(im)
draw.text((50, 50), "hey")
im.rotate(45).show()
To do what you really want you may need to make a bunch of separate correctly rotated text images and then compose them all together with some more fancy manipulation. And after all that it still may not look great. I'm not sure how antialiasing and such is handled for instance, but it might not be good. Good luck, and if anyone has an easier way, I'd be interested to know as well.
If you a using aggdraw, you can use settransform() to rotate the text. It's a bit undocumented, since effbot.org is offline.
# Matrix operations
def translate(x, y):
return np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])
def rotate(angle):
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
def draw_text(image, text, font, x, y, angle):
"""Draw text at x,y and rotated angle radians on the given PIL image"""
m = np.matmul(translate(x, y), rotate(angle))
transform = [m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]]
draw = aggdraw.Draw(image)
draw.settransform(transform)
draw.text((tx, ty), text, font)
draw.settransform()
draw.flush()

Categories