Cairo clip creates unwanted edges - python

I am using pyCairo for drawing moving elements on a surface.
In order to get better perfomance i tried to use "clip" function to redraw only the changed parts of a bigger image . Unfortunately it creates unwanted edges on the image. The edges of the cliping can be seen. Is it possible to avoid this kind of behaviour?
import math
import cairo
def draw_stuff(ctx):
""" clears background with solid black and then draws a circle"""
ctx.set_source_rgb (0, 0, 0) # Solid color
ctx.paint()
ctx.arc (0.5, 0.5, 0.5, 0, 2*math.pi)
ctx.set_source_rgb (0, 123, 0)
ctx.fill()
WIDTH, HEIGHT = 256, 256
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context (surface)
ctx.scale (WIDTH, HEIGHT) # Normalizing the canvas
draw_stuff(ctx)
#Let's draw stuff again, this time only redrawing a small part of the image
ctx.save()
ctx.rectangle(0.2,0.2,0.2,0.2)
ctx.clip()
draw_stuff(ctx)
ctx.restore()
surface.write_to_png ("example.png") # Output to PNG

You should round your cliping coordinates to integers (in device space). See http://cairographics.org/FAQ/#clipping_performance
I don't know the Python API and I am just guessing how it might work like from the C API, but it is something like this:
def snap_to_pixels(ctx, x, y):
x, y = ctx.user_to_device(x, y)
# No idea how to round an integer in python,
# this would be round() in C
# (Oh and perhaps you don't want this rounding, but
# instead want to round the top-left corner of your
# rectangle towards negative infinity and the bottom-right
# corner towards positive infinity. That way the rectangle
# would never become smaller to the rounding. But hopefully
# this example is enough to get the idea.
x = int(x + 0.5)
y = int(x + 0.5)
return ctx.device_to_user(x, y)
# Calculate the top-left and bottom-right corners of our rectangle
x1, y1 = 0.2, 0.2
x2, y2 = x1 + 0.2, y1 + 0.2
x1, y1 = snap_to_pixels(ctx, x1, y1)
x2, y2 = snap_to_pixels(ctx, x2, y2)
# Clip for this rectangle
ctx.rectangle(x1, y1, x2 - x1, y2 - y1)
ctx.clip()

Related

Getting pixel coordinates and pixel values in image region bounded by an ellipse using opencv python

I would like to draw an arbitrary ellipse on an opencv image in python and then return two arrays: (1) The pixel coordinates of all pixels bounded by the ellipse, both on the ellipse line and inside the ellipse, (2) the pixel values of each of the pixels from array (1).
I looked at this answer, but it only considers the points on the ellipse contour and not the region inside.
With this code you can get every pixel inside an ellipse:
from math import sin, cos
def get_y_ellipse(ellipse_size, x, alpha):
a, b = ellipse_size[0] / 2, ellipse_size[1] / 2
delta_sqrt = ((b**4-2*a**2*b**2+a**4)*sin(2*alpha)**2*x**2-4*a**4*x**2*cos(alpha)**2*sin(alpha)**2-4*a**2*b**2*x**2*cos(alpha)**2+4*a**4*b**2*cos(alpha)**2-4*a**2*b**2*x**2*sin(alpha)**4-4*b**4*x**2*sin(alpha)**2*cos(alpha)**2+4*a**2*b**4*sin(alpha)**2)**(1/2)
y1 = ((-b**2 + a**2)*sin(2*alpha)*x + delta_sqrt) / (2*a**2*cos(alpha)**2+2*b**2*sin(alpha)**2)
y2 = ((-b**2 + a**2)*sin(2*alpha)*x - delta_sqrt) / (2*a**2*cos(alpha)**2+2*b**2*sin(alpha)**2)
return y1, y2
ellipse_size = (100, 50)
ellipse_rotation = 45 # deg
ellipse_center_position = (0,0)
pixels = []
for x in range(ellipse_center_position[0] - ellipse_size[0], ellipse_center_position[0] + ellipse_size[0]):
y1, y2 = get_y_ellipse(ellipse_size, x, ellipse_rotation)
if complex not in map(type, (y1, y2)):
for y in range(int(y1), int(y2), -1):
pixels.append([x, y])
# 'pixels' is a 1d array that contain every pixel [x,y] format
Hope this helps.

Any way to scale up points (x, y) in Python tkinter gui?

I am working on the TSP problem and reading some points from a file (have to use the points I'm given) and want to plot the points on to a GUI. But the problem is that the points are all only 2 digit and when I plot them using the tkinter.Canvas(), they all look really smudged up and tiny, almost like they're overlapping on top of each other.
Like this:
I got the problem working but just want to use this GUI to show it working instead of outputting it all to the console but can't cause it just looks stupid even worse when the lines are being drawn. So is there some way I can scale up that canvas or modify the points some way to make them look better. Can I do really anything or am I just stuck throwing it all to the console?
Given nodes values centered at (0, 0) in any coordinate system, you need to manipulate the given coordinates to comply to what your screen and canvas needs to properly render the drawing
You can rescale your points by a scalar factor, then translate the origin to the center of the screen (or at any location for that matter), in order to visualize them more easily:
Maybe like this:
import tkinter as tk
WIDTH = 600
HEIGHT = 400
given_nodes = ((-1, -1), (-1, 1), (1, 1), (1, -1), (0, -1.5))
scale = 100
scaled_nodes = [(x * scale, y * scale) for x, y in given_nodes]
translated_to_center_nodes = [(x + WIDTH/2, y + HEIGHT/2) for x, y in scaled_nodes]
app = tk.Tk()
canvas = tk.Canvas(app, width=WIDTH, height=HEIGHT, bg='cyan')
canvas.pack()
# Draw connecting lines
line_nodes = translated_to_center_nodes + [translated_to_center_nodes[0]]
for idx, node in enumerate(line_nodes[:-1]):
x0, y0 = node
x1, y1 = line_nodes[idx+1]
canvas.create_line(x0, y0, x1, y1, fill='black')
# draw nodes
for node in translated_to_center_nodes:
x, y = node
dx, dy = 2, 2
canvas.create_oval(x-dx, y+dy, x+dx, y-dy, fill='white')
# draw origin & coordinate system at rescaled drawing scale
canvas.create_line(0, 0, 0 + scale, 0, width=9, fill='blue', arrow=tk.LAST)
canvas.create_line(0, 0, 0, scale, width=9, fill='blue', arrow=tk.LAST)
canvas.create_text(40, 40, text='SCALED\nCANVAS\nORIGIN')
# draw moved origin & coordinate system at rescaled drawing scale
canvas.create_line(0, HEIGHT/2, WIDTH, HEIGHT/2, fill='black', dash=(1, 3))
canvas.create_line(WIDTH/2, HEIGHT/2, WIDTH/2 + scale, HEIGHT/2, width=3, fill='black', arrow=tk.LAST)
canvas.create_line(WIDTH/2, 0, WIDTH/2, HEIGHT, fill='black', dash=(1, 3))
canvas.create_line(WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2 + scale, width=3, fill='black', arrow=tk.LAST)
canvas.create_text(WIDTH/2, HEIGHT/2, text='MOVED\nORIGIN')
if __name__ == '__main__':
app.mainloop()
This is commonly done with matrix multiplication and homogeneous coordinates (look it up), but the machinery needed to demonstrate a simple example is a little too heavy.
The process of using the coordinates of an object and drawing it at scale, at the proper place, maybe rotated of skewed is called instantiation (look it up too!)

How to check if the command cv2.line has changed the image in python opencv

I have a image (black and white) with a high resolution and I need the information if my drawing command (e.g cv2.line(...)) has changed the image. Comment: There is the probability, that the pixel are already in the color of cv.line(), then the image has not changed.
Currently I am comparing the whole image, whcih is very slow (I have to do this check several 1000 times.)
img = LARGE IMAGE
#make copy
imgBuffer= img.copy()
#draw on copy
imgBuffer= cv2.line(imgBuffer, point1, point2, colorBlack, 1);
# calc if there is any difference in the images
diffExist = np.any(cv2.absdiff(drawnImageBuffer, contourImage))
Does somebody have an better more efficient idea to do it?
An obvious speed improvement you can make is by only comparing the parts of the image that can possibly have changed by drawing a line connecting your two given points-- the sub-image bounded by the two points that you pass in to cv2.line().
So it would be faster to run:
img_buffer = orig_img.copy()
x1, y1 = point1
x2, y2 = point2
# make sure x1 and y1 are the lower values
if x2 < x1:
x1, x2 = x2, x1
if y2 < y1:
y1, y2 = y2, y1
cv2.line(img_buffer, point1, point2, colorBlack, 1)
diff_exist = np.any(cv2.absdiff(img_buffer[y1:y2, x1:x2],
orig_img[y1:y2, x1:x2]))

Drawing a cross on an image with OpenCV

Context: I am performing Object Localisation and wanting to implement an Inhibition of Return mechanism (i.e. drawing a black cross on the image where the red bounding box is after a trigger action.)
Problem: I do not know how to accurately scale the bounding box (red) in relation to the original input (init_input). If this scaling is understood, then the black cross should be accurately placed in the middle of the red bounding box.
My current code for this function is as follows:
def IoR(b, init_input, prev_coord):
"""
Inhibition-of-Return mechanism.
Marks the region of the image covered by
the bounding box with a black cross.
:param b:
The current bounding box represented as [x1, y1, x2, y2].
:param init_input:
The initial input volume of the current episode.
:param prev_coord:
The previous state's bounding box coordinates (x1, y1, x2, y2)
"""
x1, y1, x2, y2 = prev_coord
width = 12
x_mid = (b[2] + b[0]) // 2
y_mid = (b[3] + b[1]) // 2
# Define vertical rectangle coordinates
ver_x1 = int(((x_mid) * IMG_SIZE / (x2 - x1)) - width)
ver_x2 = int(((x_mid) * IMG_SIZE / (x2 - x1)) + width)
ver_y1 = int((b[1]) * IMG_SIZE / (y2 - y1))
ver_y2 = int((b[3]) * IMG_SIZE / (y2 - y1))
# Define horizontal rectangle coordinates
hor_x1 = int((b[0]) * IMG_SIZE / (x2 - x1))
hor_x2 = int((b[2]) * IMG_SIZE / (x2 - x1))
hor_y1 = int(((y_mid) * IMG_SIZE / (y2 - y1)) - width)
hor_y2 = int(((y_mid) * IMG_SIZE / (y2 - y1)) + width)
# Draw vertical rectangle
cv2.rectangle(init_input, (ver_x1, ver_y1), (ver_x2, ver_y2), (0, 0, 0), -1)
# Draw horizontal rectangle
cv2.rectangle(init_input, (hor_x1, hor_y1), (hor_x2, hor_y2), (0, 0, 0), -1)
The desired effect can be seen below:
Note: I believe the complexity in this problem arises due to the image being resized (to 224, 224, 3) each time I take an action (and consequently move onto the next state). Therefore, the "anchor" to determine the scaling must be extracted from the previous states scaling, which is shown in the following code:
def next_state(init_input, b_prime, g):
"""
Returns the observable region of the next state.
Formats the next state's observable region, defined
by b_prime, to be of dimension (224, 224, 3). Adding 16
additional pixels of context around the original bounding box.
The ground truth box must be reformatted according to the
new observable region.
IMG_SIZE = 224
:param init_input:
The initial input volume of the current episode.
:param b_prime:
The subsequent state's bounding box.
:param g: (init_g)
The initial ground truth box of the target object.
"""
# Determine the pixel coordinates of the observable region for the following state
context_pixels = 16
x1 = max(b_prime[0] - context_pixels, 0)
y1 = max(b_prime[1] - context_pixels, 0)
x2 = min(b_prime[2] + context_pixels, IMG_SIZE)
y2 = min(b_prime[3] + context_pixels, IMG_SIZE)
# Determine observable region
observable_region = cv2.resize(init_input[y1:y2, x1:x2], (224, 224), interpolation=cv2.INTER_AREA)
# Resize ground truth box
g[0] = int((g[0] - x1) * IMG_SIZE / (x2 - x1)) # x1
g[1] = int((g[1] - y1) * IMG_SIZE / (y2 - y1)) # y1
g[2] = int((g[2] - x1) * IMG_SIZE / (x2 - x1)) # x2
g[3] = int((g[3] - y1) * IMG_SIZE / (y2 - y1)) # y2
return observable_region, g, (b_prime[0], b_prime[1], b_prime[2], b_prime[3])
Explanation:
There is a state t in which the agent is predicting the location of the target object. The target object has a ground truth box (yellow in image, dotted in sketch), and the agent's current "localising box" is the red bounding box. Say, at state t the agent decides it is best to move right. Consequently, the bounding box is moved to the right, and then the next state, t' is determined by adding an additional 16 pixels of context around the red bounding box, cropping the original image with respect to this boundary, and then upscaling the cropped image back to 224, 224 in dimensions.
Say the agent is now confident that its prediction is accurate, so it chooses the trigger action. This basically means, end the current target object's localisation episode and place a black cross on where the agent predicted the object was (i.e. in the middle of the red bounding box). Now, since the current state is zoomed in after being cropped following the previous action, the bounding box must be re-scaled with respect to the normal/original/initial image and then the black cross can be drawn accurately onto the image.
In the context of my problem, the first rescaling between states is working perfectly well (the second code in this post). However, scaling back to normal and drawing the black cross is what I cannot seem to get my head around.
Here is an image which hopefully helps the explanation:
Here is the output of my current solution (please click the image to zoom in):
I think it's better to save the coordinate globally instead of using a bunch of upscale/downscale. They give me headache and there might be loss of precision due to rounding.
That is, every time you detect something, you convert it to global (original image) coordinate first. I have written a small demo here, imitating your detection and trigger behavior.
Initial detection:
Zoomed in, another detection:
Zoomed in, another detection:
Zoomed in, another detection:
Zoomed back to original scale, with the detection box in the correct location
Code:
import cv2
import matplotlib.pyplot as plt
IMG_SIZE = 224
im = cv2.cvtColor(cv2.imread('lena.jpg'), cv2.COLOR_BGR2GRAY)
im = cv2.resize(im, (IMG_SIZE, IMG_SIZE))
# Your detector results
detected_region = [
[(10, 20) , (80, 100)],
[(50, 0) , (220, 190)],
[(100, 143) , (180, 200)],
[(110, 45) , (180, 150)]
]
# Global states
x_scale = 1.0
y_scale = 1.0
x_shift = 0
y_shift = 0
x1, y1 = 0, 0
x2, y2 = IMG_SIZE-1, IMG_SIZE-1
for region in detected_region:
# Detection
x_scale = IMG_SIZE / (x2-x1)
y_scale = IMG_SIZE / (y2-y1)
x_shift = x1
y_shift = y1
cur_im = cv2.resize(im[y1:y2, x1:x2], (IMG_SIZE, IMG_SIZE))
# Assuming the detector return these results
cv2.rectangle(cur_im, region[0], region[1], (255))
plt.imshow(cur_im)
plt.show()
# Zooming in, using part of your code
context_pixels = 16
x1 = max(region[0][0] - context_pixels, 0) / x_scale + x_shift
y1 = max(region[0][1] - context_pixels, 0) / y_scale + y_shift
x2 = min(region[1][0] + context_pixels, IMG_SIZE) / x_scale + x_shift
y2 = min(region[1][1] + context_pixels, IMG_SIZE) / y_scale + y_shift
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
# Assuming the detector confirm its choice here
print('Confirmed detection: ', x1, y1, x2, y2)
# This time no padding
x1 = detected_region[-1][0][0] / x_scale + x_shift
y1 = detected_region[-1][0][1] / y_scale + y_shift
x2 = detected_region[-1][1][0] / x_scale + x_shift
y2 = detected_region[-1][1][1] / y_scale + y_shift
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
cv2.rectangle(im, (x1, y1), (x2, y2), (255, 0, 0))
plt.imshow(im)
plt.show()
This also prevents resizing on a resized image which might create more artifacts and worsen the detector's performance.
Imagine a point (x, y) in a 500x500 image. Let it be (100, 200).
After scaling it to a different size, say 250x250 - the correct way to scale it would be to just look at the current co-ordinate and do new_coord = old_coord * NEW_SIZE/OLD_SIZE.
Thus, (100,200) will be transformed to (50,100)
If you replace your scaling using x2-x1 and use a simpler rescaling formula, it should fix your problem.
Update: NEW_SIZE and OLD_SIZE may be different for the two co-ordinates based on the shape of the original image and final image, if they are rectangular and not square.

How to keep text inside a circle using Cairo?

I a drawing a graph using Cairo (pycairo specifically) and I need to know how can I draw text inside a circle without overlapping it, by keeping it inside the bounds of the circle. I have this simple code snippet that draws a letter "a" inside the circle:
'''
Created on May 8, 2010
#author: mrios
'''
import cairo, math
WIDTH, HEIGHT = 1000, 1000
#surface = cairo.PDFSurface ("/Users/mrios/Desktop/exampleplaces.pdf", WIDTH, HEIGHT)
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context (surface)
ctx.scale (WIDTH/1.0, HEIGHT/1.0) # Normalizing the canvas
ctx.rectangle(0, 0, 1, 1) # Rectangle(x0, y0, x1, y1)
ctx.set_source_rgb(255,255,255)
ctx.fill()
ctx.arc(0.5, 0.5, .4, 0, 2*math.pi)
ctx.set_source_rgb(0,0,0)
ctx.set_line_width(0.03)
ctx.stroke()
ctx.arc(0.5, 0.5, .4, 0, 2*math.pi)
ctx.set_source_rgb(0,0,0)
ctx.set_line_width(0.01)
ctx.set_source_rgb(255,0,255)
ctx.fill()
ctx.set_source_rgb(0,0,0)
ctx.select_font_face("Georgia",
cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
ctx.set_font_size(1.0)
x_bearing, y_bearing, width, height = ctx.text_extents("a")[:4]
print ctx.text_extents("a")[:4]
ctx.move_to(0.5 - width / 2 - x_bearing, 0.5 - height / 2 - y_bearing)
ctx.show_text("a")
surface.write_to_png ("/Users/mrios/Desktop/node.png") # Output to PNG
The problem is that my labels have variable amount of characters (with a limit of 20) and I need to set the size of the font dynamically. It must fit inside the circle, no matter the size of the circle nor the size of the label. Also, every label has one line of text, no spaces, no line breaks.
Any suggestion?
I had a similar issue, where I need to adjust the size of the font to keep the name of my object within the boundaries of rectangles, not circles. I used a while loop, and kept checking the text extent size of the string, decreasing the font size until it fit.
Here what I did: (this is using C++ under Kylix, a Delphi derivative).
double fontSize = 20.0;
bool bFontFits = false;
while (bFontFits == false)
{
m_pCanvas->Font->Size = (int)fontSize;
TSize te = m_pCanvas->TextExtent(m_name.c_str());
if (te.cx < (width*0.90)) // Allow a little room on each side
{
// Calculate the position
m_labelOrigin.x = rectX + (width/2.0) - (te.cx/2);
m_labelOrigin.y = rectY + (height/2.0) - te.cy/2);
m_fontSize = fontSize;
bFontFits = true;
break;
}
fontSize -= 1.0;
}
Of course, this doesn't show error checking. If the rectangle (or your circle) is too small, you'll have to break out of the loop.
Since the size of the circle does not matter you should draw them in the opposite order than your code.
Print the text on screen
Calculate the text boundaries (using text extents)
Draw a circle around the text that is just a little bigger from the text.

Categories

Resources