I have a set of two images of the sae scene, taken from different views, and I'm trying to use the epipolar geometry to compute the distance of the object to the camera.
Considering a point on the left image, I'm trying to find the matching point on the right image. To do this, I compute the fundamental matrix of the scene, then I compute the epipolar line of the point on the right image. After that, I think I would be able to find the matching point on the line by testing point of the line and use the condition x.T # F # x' = 0.
The problem is that the point I find is wrong. When I draw the epiline on the right image, it semms perfect as the matching point belongs to it. But when I make sure my fundamental matrix is right, the condition mentionned before is never right, the result is far from 0.
Here is my code:
discret = 500
pointg = np.array([mx, my, 1])
pts1 = np.array([[mx, my]])
lines2 = cv2.computeCorrespondEpilines(pts1, 1, F)
lines2 = lines2.reshape(-1, 3)
r, c, rien = frame1.shape
x0, y0 = map(int, [0, -lines2[0, 2] / lines2[0, 1]])
x1, y1 = map(int, [c, -(lines2[0, 2] + lines2[0, 0] * c) / lines2[0, 1]])
a = (y1 - y0) / (x1 - x0)
x, y = x0, y0
k = (x1 - x0) / discret
matd = np.array([[x], [y], [1]])
produit = (pointg # F) # matd
mini = produit
x_selec, y_selec = x, y
for i in range(discret):
x += k
y = a * x + y0
matd = np.array([[x], [y], [1]])
produit = (pointg # F) # matd
if abs(produit) < mini:
mini = produit
x_selec, y_selec = int(x), int(y)
pointd = np.array([x_selec, y_selec, 1])
(frame1 is an image read with opencv)
Since the result will surely not be 0, I'm trying to find the minimum of the values, but the programm returns x_selec, y_selec = x0, y0
And when I compute by myself the product x.T # F # x', the result is never right, despite my epiline being right.
Does anyone know how to fix this ?
I am attempting to calculate the area of the blue region and the area of yellow region:
In this graph: y=blue, peak_line=green, thresh=orange.
I am using this code:
idx = np.argwhere(np.diff(np.sign(y - peak_line))).flatten()
bounds = [1077.912, 1078.26, 1078.336, 1078.468, 1078.612, 1078.78, 1078.828, 1078.88, 1079.856, 1079.86]
plt.plot(x, y, x, thresh, x, peak_line)
plt.fill_between(x, y, thresh, where=(y>=peak_line),interpolate=True, color='#fff8ba')
plt.fill_between(x, thresh, peak_line, where=(y<=peak_line),interpolate=True, color='#fff8ba')
plt.fill_between(x, y, peak_line, where=(y>=peak_line) & (x>=x[idx][0]) & (x<=bounds[-1]), interpolate=True, color='#CDEAFF')
plt.plot(x[idx], y[idx], 'ro')
plt.show()
estimated_y = interp1d(x, y, kind='cubic')
estimated_peak_line = interp1d(x, peak_line, kind='cubic')
estimated_thresh = interp1d(x, thresh, kind='cubic')
yellow_areas = []
blue_areas = []
for i in range(len(bounds) - 1):
midpoint = (bounds[i] + bounds[i+1]) / 2
if estimated_y(midpoint) < estimated_peak_line(midpoint):
above_peak_line = abs(integrate.quad(estimated_peak_line, bounds[i], bounds[i+1])[0])
above_thresh_line = abs(integrate.quad(estimated_thresh, bounds[i], bounds[i+1])[0])
yellow_areas.append(above_peak_line - above_thresh_line)
else:
above_peak_line = abs(integrate.quad(estimated_peak_line, bounds[i], bounds[i+1])[0])
above_y = abs(integrate.quad(estimated_y, bounds[i], bounds[i+1])[0])
blue_areas.append(above_peak_line - above_y)
print(sum(yellow_areas))
print(sum(blue_areas))
4.900000000000318
2.999654602006661
I thought I calculated the area of the blue region and the area of yellow region correct, until I calculated the area of the polygon:
bunch_of_xs = np.linspace(min(x), max(x), num=10000, endpoint=True)
final_curve = estimated_y(bunch_of_xs)
final_thresh = estimated_thresh(bunch_of_xs)
final_peak_line = estimated_peak_line(bunch_of_xs)
def PolygonArea(corners):
n = len(corners) # of corners
area = 0.0
for i in range(n):
j = (i + 1) % n
area += corners[i][0] * corners[j][1]
area -= corners[j][0] * corners[i][1]
area = abs(area) / 2.0
return area
vertex1 = (bunch_of_xs[0], final_thresh[0])
vertex2 = (bunch_of_xs[-1], final_thresh[-1])
vertex3 = (x[idx][-1], y[idx][-1])
vertex4 = (x[idx][0], y[idx][0])
coords = (vertex1,vertex2,vertex3,vertex4)
plt.plot(x, y, 'o', bunch_of_xs, final_curve, '--', bunch_of_xs, final_thresh, bunch_of_xs, final_peak_line)
x_val = [x[0] for x in coords]
y_val = [x[1] for x in coords]
plt.plot(x_val,y_val,'or')
print("Coordinates of total polygon:", coords)
print("Total polygon area:", PolygonArea(coords))
Coordinates of total polygon: ((1077.728, -41.30177170550451), (1079.96, -42.254314285935834), (1079.86, -49.207348695828706), (1077.912, -48.271572477115136))
Total polygon area: 14.509708069890621
The sum of the area of the blue region and the area of yellow region should equal the total polygon area.
4.900000000000318 + 2.999654602006661 ≠ 14.509708069890621
What am I doing wrong?
Edit: This code will be used for many different graphs. Not all graphs look the same. For example, this graph has 3 blue regions and so I have to calculate the area of all 3 blue regions and add them together to get the total blue area. Every graph has a different amount of blue regions (some only have 1 region). So, I have to make the code flexible to account for the possibility of a graph having multiple blue regions to add together to get the total blue region area.
Since I don't have all of your data I will give something between pseudo-code and implementation.
Say we have arrays x (x-axis), y1 (data), y2 (some line which bounds the parts over which we want to integrate).
First step: Iterate over your bounds array and see which parts we want to integrate over. I assume that you have the bounds array already, as your question suggests.
def get_pairs_of_idxs(x, y1, y2, bounds):
lst_pairs = []
for i in range(len(bounds)-1):
x0, x1 = bounds[i], bounds[i+1]
xc = 0.5 * (x0 + x1) # we want to see if the straight line y2 is above or below, so we take one x value and test it
indx_xc = np.searchsorted(x, xc) # this returns us the index at which xc is located
y1c, y2c = y1[indx_xc], y2[indx_xc]
if y2c < y1c: # then the line is below the curve, so we want to integrate
lst_pairs.append((x0, x1))
Now we have a list of pairs of indices, between which we want to integrate.
def solution(x, y1, y2, bounds):
tot_area = 0
lst_pairs = get_pairs_of_idxs(x, y1, y2, bounds)
for x0, x1 in lst_pairs:
mask = np.logical_and(x >= x0, x <= x1) # relevant places in x and y data
xs = x[mask] # the x values along which we integrate
ys = (y2 - y1)[mask] # we want to integrate the difference of the curves
tot_area += np.trapz(ys, xs)
return tot_area
That's what I was thinking about.
In general, the area between two curves f(x) and g(x) is integral(g(x) - f(x)).
So say we have two curves:
xvals = np.linspace(0, 1, 100)
yvals_1 = np.sin(xvals * 10)
yvals_2 = 0.5 - 0.5 * xvals
plt.plot(xvals, yvals_1, '-b')
plt.plot(xvals, yvals_2, '-g')
The "transformed" curve becomes:
yvals_3 = yvals_1 - yvals_2
plt.plot(xvals, yvals_3, '--r')
plt.plot(xvals, np.zeros(xvals.shape), '--k')
And since we want to ignore everything under the green line,
yvals_3[yvals_3 < 0] = 0
plt.plot(xvals, yvals_3, '-r')
Since you want to impose additional constraints, such as "only the area between the first and last intersections", do that now.
# Cheating a little bit -- but you already know how to get the intersections.
first_intersection_x = xvals[4]
last_intersection_x = xvals[94]
cfilter = np.logical_and(xvals >= first_intersection_x, xvals <= last_intersection_x)
xvals_calc = xvals[cfilter]
yvals_calc = yvals_3[cfilter]
The area under this curve is easily calculated using np.trapz
area_under_curve = np.trapz(yvals_calc, xvals_calc)
Of course, this answer assumes that yvals_1 and yvals_2 are available at the same xvals. If not, interpolation is easy.
h, theta, d = transform.hough_line(outlines)
for acum, angle, dist in zip(*transform.hough_line_peaks(h, theta, d)):
y0 = (dist - 0 * np.cos(angle)) / np.sin(angle)
y1 = (dist - outlines.shape[1] * np.cos(angle)) / np.sin(angle)
x0 = ...
x1 = ...
rr,cc,_ = draw.line_aa(x0,y0,x1,y1)
What I want is the x0 and x1 values between the range of my outline shape, that is 640,640 (2D). And I want to scale the y0 and y1 to the size of my outline.shape.
The y0, y1 coordinate that you calculate with that formula correspond to where the line intersects the edges of your image. That is why it includes 0 and outlines.shape[1].
y0 corresponds to the row where the line intersects column 0, hence 0 * cos(angle).
y1 corresponds to the row where the line intersects the last column of your image, i.e. its width (which is outlines.shape[1])
So you can draw a line from (0, y0) to (width, y1) to emphasize the detected lines. Use, for example outlines[line(0, y0, width-1, y1] = 1. Note I put width - 1 because indexing starts from 0 and width is out of bounds. This is not the case in your formula because it is subtracted from dist
This tutorial illustrates well how it works and how to add the discovered lines to your image (in the first part). Replace the uninspired X-shaped image with an image of your choice and you will see the lines. Your image should ideally be binarised and have not too many points, so try passing it through and edge detector (Canny) or a skeletonization procedure.
I'm currently working on a pygame game and I need to place objects randomly on the screen, except they cannot be within a designated rectangle. Is there an easy way to do this rather than continuously generating a random pair of coordinates until it's outside of the rectangle?
Here's a rough example of what the screen and the rectangle look like.
______________
| __ |
| |__| |
| |
| |
|______________|
Where the screen size is 1000x800 and the rectangle is [x: 500, y: 250, width: 100, height: 75]
A more code oriented way of looking at it would be
x = random_int
0 <= x <= 1000
and
500 > x or 600 < x
y = random_int
0 <= y <= 800
and
250 > y or 325 < y
Partition the box into a set of sub-boxes.
Among the valid sub-boxes, choose which one to place your point in with probability proportional to their areas
Pick a random point uniformly at random from within the chosen sub-box.
This will generate samples from the uniform probability distribution on the valid region, based on the chain rule of conditional probability.
This offers an O(1) approach in terms of both time and memory.
Rationale
The accepted answer along with some other answers seem to hinge on the necessity to generate lists of all possible coordinates, or recalculate until there is an acceptable solution. Both approaches take more time and memory than necessary.
Note that depending on the requirements for uniformity of coordinate generation, there are different solutions as is shown below.
First attempt
My approach is to randomly choose only valid coordinates around the designated box (think left/right, top/bottom), then select at random which side to choose:
import random
# set bounding boxes
maxx=1000
maxy=800
blocked_box = [(500, 250), (100, 75)]
# generate left/right, top/bottom and choose as you like
def gen_rand_limit(p1, dim):
x1, y1 = p1
w, h = dim
x2, y2 = x1 + w, y1 + h
left = random.randrange(0, x1)
right = random.randrange(x2+1, maxx-1)
top = random.randrange(0, y1)
bottom = random.randrange(y2, maxy-1)
return random.choice([left, right]), random.choice([top, bottom])
# check boundary conditions are met
def check(x, y, p1, dim):
x1, y1 = p1
w, h = dim
x2, y2 = x1 + w, y1 + h
assert 0 <= x <= maxx, "0 <= x(%s) <= maxx(%s)" % (x, maxx)
assert x1 > x or x2 < x, "x1(%s) > x(%s) or x2(%s) < x(%s)" % (x1, x, x2, x)
assert 0 <= y <= maxy, "0 <= y(%s) <= maxy(%s)" %(y, maxy)
assert y1 > y or y2 < y, "y1(%s) > y(%s) or y2(%s) < y(%s)" % (y1, y, y2, y)
# sample
points = []
for i in xrange(1000):
x,y = gen_rand_limit(*blocked_box)
check(x, y, *blocked_box)
points.append((x,y))
Results
Given the constraints as outlined in the OP, this actually produces random coordinates (blue) around the designated rectangle (red) as desired, however leaves out any of the valid points that are outside the rectangle but fall within the respective x or y dimensions of the rectangle:
# visual proof via matplotlib
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
X,Y = zip(*points)
fig = plt.figure()
ax = plt.scatter(X, Y)
p1 = blocked_box[0]
w,h = blocked_box[1]
rectangle = Rectangle(p1, w, h, fc='red', zorder=2)
ax = plt.gca()
plt.axis((0, maxx, 0, maxy))
ax.add_patch(rectangle)
Improved
This is easily fixed by limiting only either x or y coordinates (note that check is no longer valid, comment to run this part):
def gen_rand_limit(p1, dim):
x1, y1 = p1
w, h = dim
x2, y2 = x1 + w, y1 + h
# should we limit x or y?
limitx = random.choice([0,1])
limity = not limitx
# generate x, y O(1)
if limitx:
left = random.randrange(0, x1)
right = random.randrange(x2+1, maxx-1)
x = random.choice([left, right])
y = random.randrange(0, maxy)
else:
x = random.randrange(0, maxx)
top = random.randrange(0, y1)
bottom = random.randrange(y2, maxy-1)
y = random.choice([top, bottom])
return x, y
Adjusting the random bias
As pointed out in the comments this solution suffers from a bias given to points outside the rows/columns of the rectangle. The following fixes that in principle by giving each coordinate the same probability:
def gen_rand_limit(p1, dim):
x1, y1 = p1Final solution -
w, h = dim
x2, y2 = x1 + w, y1 + h
# generate x, y O(1)
# --x
left = random.randrange(0, x1)
right = random.randrange(x2+1, maxx)
withinx = random.randrange(x1, x2+1)
# adjust probability of a point outside the box columns
# a point outside has probability (1/(maxx-w)) v.s. a point inside has 1/w
# the same is true for rows. adjupx/y adjust for this probability
adjpx = ((maxx - w)/w/2)
x = random.choice([left, right] * adjpx + [withinx])
# --y
top = random.randrange(0, y1)
bottom = random.randrange(y2+1, maxy)
withiny = random.randrange(y1, y2+1)
if x == left or x == right:
adjpy = ((maxy- h)/h/2)
y = random.choice([top, bottom] * adjpy + [withiny])
else:
y = random.choice([top, bottom])
return x, y
The following plot has 10'000 points to illustrate the uniform placement of points (the points overlaying the box' border are due to point size).
Disclaimer: Note that this plot places the red box in the very middle such thattop/bottom, left/right have the same probability among each other. The adjustment thus is relative to the blocking box, but not for all areas of the graph. A final solution requires to adjust the probabilities for each of these separately.
Simpler solution, yet slightly modified problem
It turns out that adjusting the probabilities for different areas of the coordinate system is quite tricky. After some thinking I came up with a slightly modified approach:
Realizing that on any 2D coordinate system blocking out a rectangle divides the area into N sub-areas (N=8 in the case of the question) where a valid coordinate can be chosen. Looking at it this way, we can define the valid sub-areas as boxes of coordinates. Then we can choose a box at random and a coordinate at random from within that box:
def gen_rand_limit(p1, dim):
x1, y1 = p1
w, h = dim
x2, y2 = x1 + w, y1 + h
# generate x, y O(1)
boxes = (
((0,0),(x1,y1)), ((x1,0),(x2,y1)), ((x2,0),(maxx,y1)),
((0,y1),(x1,y2)), ((x2,y1),(maxx,y2)),
((0,y2),(x1,maxy)), ((x1,y2),(x2,maxy)), ((x2,y2),(maxx,maxy)),
)
box = boxes[random.randrange(len(boxes))]
x = random.randrange(box[0][0], box[1][0])
y = random.randrange(box[0][1], box[1][1])
return x, y
Note this is not generalized as the blocked box may not be in the middle hence boxes would look different. As this results in each box chosen with the same probability, we get the same number of points in each box. Obviously the densitiy is higher in smaller boxes:
If the requirement is to generate a uniform distribution among all possible coordinates, the solution is to calculate boxes such that each box is about the same size as the blocking box. YMMV
I've already posted a different answer that I still like, as it is simple and
clear, and not necessarily slow... at any rate it's not exactly what the OP asked for.
I thought about it and I devised an algorithm for solving the OP's problem within their constraints:
partition the screen in 9 rectangles around and comprising the "hole".
consider the 8 rectangles ("tiles") around the central hole"
for each tile, compute the origin (x, y), the height and the area in pixels
compute the cumulative sum of the areas of the tiles, as well as the total area of the tiles
for each extraction, choose a random number between 0 and the total area of the tiles (inclusive and exclusive)
using the cumulative sums determine in which tile the random pixel lies
using divmod determine the column and the row (dx, dy) in the tile
using the origins of the tile in the screen coordinates, compute the random pixel in screen coordinates.
To implement the ideas above, in which there is an initialization phase in which we compute static data and a phase in which we repeatedly use those data, the natural data structure is a class, and here it is my implementation
from random import randrange
class make_a_hole_in_the_screen():
def __init__(self, screen, hole_orig, hole_sizes):
xs, ys = screen
x, y = hole_orig
wx, wy = hole_sizes
tiles = [(_y,_x*_y) for _x in [x,wx,xs-x-wx] for _y in [y,wy,ys-y-wy]]
self.tiles = tiles[:4] + tiles[5:]
self.pixels = [tile[1] for tile in self.tiles]
self.total = sum(self.pixels)
self.boundaries = [sum(self.pixels[:i+1]) for i in range(8)]
self.x = [0, 0, 0,
x, x,
x+wx, x+wx, x+wx]
self.y = [0, y, y+wy,
0, y+wy,
0, y, y+wy]
def choose(self):
n = randrange(self.total)
for i, tile in enumerate(self.tiles):
if n < self.boundaries[i]: break
n1 = n - ([0]+self.boundaries)[i]
dx, dy = divmod(n1,self.tiles[i][0])
return self.x[i]+dx, self.y[i]+dy
To test the correctness of the implementation, here it is a rough check that I
run on python 2.7,
drilled_screen = make_a_hole_in_the_screen((200,100),(30,50),(20,30))
for i in range(1000000):
x, y = drilled_screen.choose()
if 30<=x<50 and 50<=y<80: print "***", x, y
if x<0 or x>=200 or y<0 or y>=100: print "+++", x, y
A possible optimization consists in using a bisection algorithm to find the relevant tile in place of the simpler linear search that I've implemented.
It requires a bit of thought to generate a uniformly random point with these constraints. The simplest brute force way I can think of is to generate a list of all valid points and use random.choice() to select from this list. This uses a few MB of memory for the list, but generating a point is very fast:
import random
screen_width = 1000
screen_height = 800
rect_x = 500
rect_y = 250
rect_width = 100
rect_height = 75
valid_points = []
for x in range(screen_width):
if rect_x <= x < (rect_x + rect_width):
for y in range(rect_y):
valid_points.append( (x, y) )
for y in range(rect_y + rect_height, screen_height):
valid_points.append( (x, y) )
else:
for y in range(screen_height):
valid_points.append( (x, y) )
for i in range(10):
rand_point = random.choice(valid_points)
print(rand_point)
It is possible to generate a random number and map it to a valid point on the screen, which uses less memory, but it is a bit messy and takes more time to generate the point. There might be a cleaner way to do this, but one approach using the same screen size variables as above is here:
rand_max = (screen_width * screen_height) - (rect_width * rect_height)
def rand_point():
rand_raw = random.randint(0, rand_max-1)
x = rand_raw % screen_width
y = rand_raw // screen_width
if rect_y <= y < rect_y+rect_height and rect_x <= x < rect_x+rect_width:
rand_raw = rand_max + (y-rect_y) * rect_width + (x-rect_x)
x = rand_raw % screen_width
y = rand_raw // screen_width
return (x, y)
The logic here is similar to the inverse of the way that screen addresses are calculated from x and y coordinates on old 8 and 16 bit microprocessors. The variable rand_max is equal to the number of valid screen coordinates. The x and y co-ordinates of the pixel are calculated, and if it is within the rectangle the pixel is pushed above rand_max, into the region that couldn't be generated with the first call.
If you don't care too much about the point being uniformly random, this solution is easy to implement and very quick. The x values are random, but the Y value is constrained if the chosen X is in the column with the rectangle, so the pixels above and below the rectangle will have a higher probability of being chosen than pizels to the left and right of the rectangle:
def pseudo_rand_point():
x = random.randint(0, screen_width-1)
if rect_x <= x < rect_x + rect_width:
y = random.randint(0, screen_height-rect_height-1)
if y >= rect_y:
y += rect_height
else:
y = random.randint(0, screen_height-1)
return (x, y)
Another answer was calculating the probability that the pixel is in certain regions of the screen, but their answer isn't quite correct yet. Here's a version using a similar idea, calculate the probability that the pixel is in a given region and then calculate where it is within that region:
valid_screen_pixels = screen_width*screen_height - rect_width * rect_height
prob_left = float(rect_x * screen_height) / valid_screen_pixels
prob_right = float((screen_width - rect_x - rect_width) * screen_height) / valid_screen_pixels
prob_above_rect = float(rect_y) / (screen_height-rect_height)
def generate_rand():
ymin, ymax = 0, screen_height-1
xrand = random.random()
if xrand < prob_left:
xmin, xmax = 0, rect_x-1
elif xrand > (1-prob_right):
xmin, xmax = rect_x+rect_width, screen_width-1
else:
xmin, xmax = rect_x, rect_x+rect_width-1
yrand = random.random()
if yrand < prob_above_rect:
ymax = rect_y-1
else:
ymin=rect_y+rect_height
x = random.randrange(xmin, xmax)
y = random.randrange(ymin, ymax)
return (x, y)
If it's the generation of random you want to avoid, rather than the loop, you can do the following:
Generate a pair of random floating point coordinates in [0,1]
Scale the coordinates to give a point in the outer rectangle.
If your point is outside the inner rectangle, return it
Rescale to map the inner rectangle to the outer rectangle
Goto step 3
This will work best if the inner rectangle is small as compared to the outer rectangle. And it should probably be limited to only going through the loop some maximum number of times before generating new random and trying again.