If I
define an intrinsic camera matrix A and poses [rvec, ...], [tvec, ...],
use them as parameters in cv2.projectPoints to generate the the images that would be generated by a camera when it views a grid of circles,
Detect the features (cv2.findCirclesGrid) in the resulting images
Use cv2.calibrateCamera on the feature detections to recover the camera parameters
Shouldn't I recover the original intrinsic and extrinsic parameters?
The full code at the bottom of this question does this process, but does not
recover the original camera parameters:
Kept 4 full captures out of 4 images
calibration error 133.796093439
Simulation matrix:
[[ 5.00000000e+03 0.00000000e+00 3.20000000e+02]
[ 0.00000000e+00 5.00000000e+03 2.40000000e+02]
[ 0.00000000e+00 0.00000000e+00 1.00000000e+00]]
Estimated matrix:
[[ 1.0331118 0. 317.58445168]
[ 0. 387.49075886 317.98450481]
[ 0. 0. 1. ]]
I.e. the mean error is huge, and the estimated camera matrix does not look like
the simulation camera matrix orginally used to generate the test images.
I'd expect that this sort of closed-loop simulation should result in a very good estimate of the intrinsic camera matrix. What am I doing wrong that this approach for validating cameraCalibration doesn't seem to work?
Edits in response to AldurDisciple comment
1) Added new function in code below direct_generation_of_points that skips
the image generation functions and uses cv2.projectPoints directly to
compute the circle locations that are passed into cv2.calibrateCamera.
This works correctly.
But this is confusing: the estimated circle locations (derived from my simulated
images) are typically within about a 10'th of a pixel from the exact ones, the main
difference is that the points are in a different order:
# compare the y-component's
In [245]: S.dots[0][:,0,1]
Out[245]:
array([ 146.33618164, 146.30953979, 146.36413574, 146.26707458,
146.17976379, 146.30110168, 146.17236328, 146.35955811,
146.33454895, 146.36776733, 146.2612915 , 146.21359253,
146.23895264, 146.27839661, 146.27764893, 177.51347351,
177.57495117, 177.53858948, 177.48587036, 177.63012695,
177.48597717, 177.51727295, 177.5202179 , 177.52545166,
177.57287598, 177.51008606, 177.51296997, 177.53715515,
177.53053284, 177.58164978, 208.69573975, 208.7252655 ,
208.69616699, 208.73510742, 208.63375854, 208.66760254,
208.71517944, 208.74360657, 208.62438965, 208.59814453,
208.67456055, 208.72662354, 208.70921326, 208.63339233,
208.70820618, 239.8401947 , 240.06373596, 239.87176514,
240.04118347, 239.97781372, 239.97572327, 240.04475403,
239.95411682, 239.80995178, 239.94726562, 240.01327515,
239.82675171, 239.99989319, 239.90107727, 240.07745361,
271.31692505, 271.28417969, 271.28216553, 271.33111572,
271.33279419, 271.33584595, 271.30758667, 271.21173096,
271.28588867, 271.3387146 , 271.33770752, 271.2104187 ,
271.38504028, 271.25054932, 271.29376221, 302.52420044,
302.47903442, 302.41482544, 302.39868164, 302.47793579,
302.49789429, 302.45016479, 302.48071289, 302.50463867,
302.51422119, 302.46307373, 302.42077637, 302.60791016,
302.48162842, 302.46142578, 333.70709229, 333.75698853,
333.64157104, 333.64926147, 333.6647644 , 333.69546509,
333.73342896, 333.76846313, 333.57540894, 333.76605225,
333.74307251, 333.60968018, 333.7739563 , 333.70132446,
333.62057495], dtype=float32)
In [246]: S.exact_dots[0][:,0,1]
Out[246]:
array([ 146.25, 177.5 , 208.75, 240. , 271.25, 302.5 , 333.75,
146.25, 177.5 , 208.75, 240. , 271.25, 302.5 , 333.75,
<< snipped 10 identical rows >>
146.25, 177.5 , 208.75, 240. , 271.25, 302.5 , 333.75,
146.25, 177.5 , 208.75, 240. , 271.25, 302.5 , 333.75,
146.25, 177.5 , 208.75, 240. , 271.25, 302.5 , 333.75], dtype=float32)
Here's the working version of what I'm trying to do:
import scipy
import cv2
import itertools
def direct_generation_of_points():
''' Skip the part where we actually generate the image,
just use cv2.projectPoints to generate the exact locations
of the grid centers.
** This seems to work correctly **
'''
S=Setup()
t=tvec(0.0,0.0,1.6) # keep the camera 1.6 meters away from target, looking at the origin
rvecs=[ rvec(0.0,0.0,0.0), rvec(0.0, scipy.pi/6,0.0), rvec(scipy.pi/8,0.0,0.0), rvec(0.0,0.0,0.5) ]
S.poses=[ (r,t) for r in rvecs ]
S.images='No images: just directly generate the extracted circle locations'
S.dots=S.make_locations_direct()
calib_flags=cv2.CALIB_ZERO_TANGENT_DIST|cv2.CALIB_SAME_FOCAL_LENGTH
calib_flags=calib_flags|cv2.CALIB_FIX_K3|cv2.CALIB_FIX_K4
calib_flags=calib_flags|cv2.CALIB_FIX_K5|cv2.CALIB_FIX_K6
S.calib_results=cv2.calibrateCamera( [S.grid,]*len(S.dots), S.dots, S.img_size, cameraMatrix=S.A, flags=calib_flags)
print "calibration error ", S.calib_results[0]
print "Simulation matrix: \n", S.A
print "Estimated matrix: \n", S.calib_results[1]
return S
def basic_test():
''' Uses a camera setup to
(1) generate an image of a grid of circles
(2) detects those circles
(3) generate an estimated camera model from the circle detections
** This does not work correctly **
'''
S=Setup()
t=tvec(0.0,0.0,1.6) # keep the camera 1.6 meters away from target, looking at the origin
rvecs=[ rvec(0.0,0.0,0.0), rvec(0.0, scipy.pi/6,0.0), rvec(scipy.pi/8,0.0,0.0), rvec(0.0,0.0,0.5) ]
S.poses=[ (r,t) for r in rvecs ]
S.images=S.make_images()
S.dots=extract_dots( S.images, S.grid_size[::-1] )
S.exact_dots=S.make_locations_direct()
calib_flags=cv2.CALIB_ZERO_TANGENT_DIST|cv2.CALIB_SAME_FOCAL_LENGTH
calib_flags=calib_flags|cv2.CALIB_FIX_K3|cv2.CALIB_FIX_K4|cv2.CALIB_FIX_K5
calib_flags=calib_flags|cv2.CALIB_FIX_K6
S.calib_results=cv2.calibrateCamera( [S.grid,]*len(S.dots), S.dots, S.img_size, cameraMatrix=S.A, flags=calib_flags)
print "calibration error ", S.calib_results[0]
print "Simulation matrix: \n", S.A
print "Estimated matrix: \n", S.calib_results[1]
return S
class Setup(object):
''' Class to simulate a camera, produces images '''
def __init__(self):
self.img_size=(480,640)
self.A=scipy.array( [ [5.0e3, 0.0, self.img_size[1]/2],
[ 0.0, 5.0e3, self.img_size[0]/2],
[ 0.0, 0.0, 1.0 ] ],
dtype=scipy.float32 )
# Nx, Ny, spacing, dot-size
self.grid_spec=( 15, 7, 0.01, 0.001 )
self.grid=square_grid_xy( self.grid_spec[0], self.grid_spec[1], self.grid_spec[2])
# a pose is a pair: rvec, tvec
self.poses=[ ( rvec(0.0, scipy.pi/6, 0.0), tvec( 0.0,0.0,1.6) ),
]
#property
def grid_size(self):
return self.grid_spec[:2]
def make_images(self):
return [make_dots_image(self.img_size, self.A, rvec, tvec, self.grid, self.grid_spec[-1] ) for (rvec,tvec) in self.poses]
def make_locations_direct(self):
return [cv2.projectPoints( self.grid, pose[0], pose[1], self.A, None)[0] for pose in self.poses]
def square_grid_xy( nx, ny, dx ):
''' Returns a square grid in the xy plane, useful
for defining test grids for camera calibration
'''
xvals=scipy.arange(nx)*dx
yvals=scipy.arange(ny)*dx
xvals=xvals-scipy.mean(xvals)
yvals=yvals-scipy.mean(yvals)
res=scipy.zeros( [3, nx*ny], dtype=scipy.float32 )
for (i,(x,y)) in enumerate( itertools.product(xvals, yvals)):
res[:,i]=scipy.array( [x,y,0.0] )
return res.transpose()
# single pixel dots were not detected?
#def make_single_pixel_dots( img_size, A, rvec, tvec, grid, dist_k=None):
# rgb=scipy.ones( img_size+(3,), dtype=scipy.uint8 )*0xff
# (dot_locs, jac)=cv2.projectPoints( grid, rvec, tvec, A, dist_k)
# for p in dot_locs:
# (c,r)=(int(p[0][0]+0.5), int(p[0][1]+0.5))
# if 0<=c<img_size[1] and 0<=r<img_size[0]:
# rgb[r,c,:]=0
# return rgb
def make_dots_image( img_size, A, rvec, tvec, grid, dotsize, dist_k=None):
''' Make the image of the dots, uses cv2.projectPoints to construct the image'''
# make white image
max_intensity=0xffffffff
intensity=scipy.ones( img_size, dtype=scipy.uint32)*max_intensity
# Monte-Carlo approach to draw the dots
for dot in grid:
deltas=2*dotsize*( scipy.rand(1024, 3 )-0.5) # no. of samples must be small relative to bit-depth of intensity array
deltas[:,2]=0
indicator=scipy.where( scipy.sum( deltas*deltas, 1)<dotsize*dotsize, 1, 0.0)
print "inside fraction: ", sum(indicator)/len(indicator)
(pts,jac)=cv2.projectPoints( dot+deltas, rvec, tvec, A, dist_k )
pts=( p for (ind,p) in zip(indicator, pts) if ind )
for p in pts:
(c,r)=( int(p[0][0]+0.5), int( p[0][1]+0.5 ) )
if r>=0 and c>=0 and c<img_size[1] and r<img_size[0]:
intensity[r,c]=intensity[r,c]-6
else:
print "col, row ", (c,r), " point rejected"
# rescale so that image goes from 0x0 to max intensity
min_intensity=min(intensity.flat)
# normalize the intensity
intensity=0xff*( (intensity-min_intensity)/float(max_intensity-min_intensity) )
pixel_img=scipy.ones( intensity.shape+(3,), dtype=scipy.uint8 )
return (pixel_img*intensity[:,:,scipy.newaxis]).astype(scipy.uint8 )
def extract_dots( img_list, grid_size ):
'''
#arg img_list: usually a list of images, can be a single image
'''
# convert single array, into a 1-element list
if type(img_list) is scipy.ndarray:
img_list=[img_list,]
def get_dots( img ):
res=cv2.findCirclesGridDefault( img, grid_size)
if not res[0]: # sometimes, reversing the grid size will make the detection successful
return cv2.findCirclesGridDefault( img, grid_size[::-1] )
return res
all_dots=[ get_dots( img) for img in img_list]
#all_dots=[cv2.findCirclesGrid( img, grid_size[::-1] ) for img in img_list ]
full_captures=[x[1] for x in all_dots if x[0] ]
print "Kept {0} full captures out of {1} images".format( len(full_captures), len(img_list) )
if len(full_captures)<len(img_list):
print "\t", [x[0] for x in all_dots]
return [scipy.squeeze(x) for x in full_captures]
# convenience functions
def vec3_32(x,y,z):
return scipy.array( [x,y,z], dtype=scipy.float32 )
rvec=vec3_32
tvec=vec3_32
if __name__=="__main__":
basic_test()
The key issue is in the organization of the grid points passed in the first argument of cv2.calibrateCamera,
in the question the points are organized in column major order, so to speak, and need to be organized in row-major order:
def square_grid_xy_fixed( nx, ny, dx ):
''' Returns a square grid in the xy plane, useful
for defining test grids for camera calibration
'''
xvals=scipy.arange(nx)*dx
yvals=scipy.arange(ny)*dx
xvals=xvals-scipy.mean(xvals)
yvals=yvals-scipy.mean(yvals)
res=scipy.zeros( [3, nx*ny], dtype=scipy.float32 )
# need to have "x" be the most rapidly varying index, i.e.
# it must be the final argument to itertools.product
for (i,(y,x)) in enumerate( itertools.product(yvals, xvals)):
res[:,i]=scipy.array( [x,y,0.0] )
return res.transpose()
Related
I am doing a License/Number plate recognition project and I'm on the stage of completion but there is a small problem, I have successfully recognized the characters, consider the below example:
This is an input image, I got the prediction as 2791 2g rj14
As you can, the ocr did a great job but the arrangement is destroyed (DESTROYING the whole purpose). Sometimes it does outputs in the correct sequence but sometimes it does not, so when it does not output in the correct sequence I'm trying to develop an algorithm which will take the predicted num_plate string as input and rearrange it on the basis of my country (India).
Below are some images which tell us about the format of Indian Number/License Plate.
Also, I have collected all the states but for right now, I just want to do for only the 3 states which are: Delhi (DL), Haryana (HR), UttarPradesh (UP). More info : https://en.wikipedia.org/wiki/List_of_Regional_Transport_Office_districts_in_India
total_states_list = [
'AN','AP','AR','AS','BR','CG','CH','DD','DL','DN','GA','GJ','HR','HP','JH','JK','KA','KL',
'LD','MH','ML','MN','MP','MZ','NL','OD','PB','PY','RJ','SK','TN','TR','TS','UK','UP','WB'
]
district_codes = {
'DL': ['1','2','3','4','5','6','7','8','9','10','11','12','13'],
'HR': [01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,
40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,
71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99
]
}
So, I have been trying but cannot come up with an algorithm which rearranges the sequence in the required sequence if it is not. Any help would be really appreciated.
Details about OCR
Using keras-ocr, I'm getting the following output for the input image:
[
('hrlz', array([[ 68.343796, 42.088367],
[196.68803 , 26.907867],
[203.00832 , 80.343094],
[ 74.66408 , 95.5236 ]], dtype=float32)),
('c1044', array([[ 50.215836, 113.09602 ],
[217.72466 , 92.58473 ],
[224.3968 , 147.07387 ],
[ 56.887985, 167.58516 ]], dtype=float32))
]
source: https://keras-ocr.readthedocs.io/en/latest/examples/using_pretrained_models.html
Inside the keras_ocr.tools.drawAnnotations they are I think getting the predictions boxes. So I located this file and found the implementation of drawAnnotations function and here it is:
def drawAnnotations(image, predictions, ax=None):
if ax is None:
_, ax = plt.subplots()
ax.imshow(drawBoxes(image=image, boxes=predictions, boxes_format='predictions'))
predictions = sorted(predictions, key=lambda p: p[1][:, 1].min())
left = []
right = []
for word, box in predictions:
if box[:, 0].min() < image.shape[1] / 2:
left.append((word, box))
else:
right.append((word, box))
ax.set_yticks([])
ax.set_xticks([])
for side, group in zip(['left', 'right'], [left, right]):
for index, (text, box) in enumerate(group):
y = 1 - (index / len(group))
xy = box[0] / np.array([image.shape[1], image.shape[0]])
xy[1] = 1 - xy[1]
ax.annotate(s=text,
xy=xy,
xytext=(-0.05 if side == 'left' else 1.05, y),
xycoords='axes fraction',
arrowprops={
'arrowstyle': '->',
'color': 'r'
},
color='r',
fontsize=14,
horizontalalignment='right' if side == 'left' else 'left')
return ax
How should I go about and get the (x,y,w,h) and then somehow sort/print according to y/x of number_plate bbox?
EDIT - 2
I managed to get the bounding box of characters as you can see in the image below:
using the function cv2.polylines(box), where box are the same coordinates where I have pasted the output earlier. Now how can I print them in a sequence like, left to right... using the y/x as suggested by people in the comments.
If you can get the coordinates of each identified text box, then:
Rotate the coordinates so the boxes are parallel with the X-axis
Scale the Y-coordinates so they can be rounded to integers, so that boxes that are side-by-side will get the same integer Y-coordinate (like a line number)
Sort the data by Y, then X coordinate
Extract the texts in that order
Here is an example of such sequence:
data = [
('hrlz', [[ 68.343796, 42.088367],
[196.68803 , 26.907867],
[203.00832 , 80.343094],
[ 74.66408 , 95.5236 ]]),
('c1044',[[ 50.215836, 113.09602 ],
[217.72466 , 92.58473 ],
[224.3968 , 147.07387 ],
[ 56.887985, 167.58516 ]])
]
# rotate data to align with X-axis
a, b = data[0][1][:2]
dist = ((b[1] - a[1]) ** 2 + (b[0] - a[0]) ** 2) ** 0.5
sin = (b[1] - a[1]) / dist
cos = (b[0] - a[0]) / dist
data = [
(text, [(x * cos + y * sin, y * cos - x * sin) for x, y in box]) for text, box in data
]
# scale Y coordinate to integers
a, b = data[0][1][1:3]
height = b[1] - a[1]
data = [
(round(box[0][1] / height), box[0][0], text)
for text, box in data
]
# sort by Y, then X
data.sort()
# Get text in the right order
print("".join(text for _, _, text in data))
This assumes that the points of the boxes are given in the following clockwise order:
top-left, top-right, bottom-right, bottom-left
In order to improve the efficiency of a python script, I am trying to convert a script based on a lot of "for loop" operations on a points cloud thanks to Numpy to speed up the process.
In a nutshell, I have a 3d model represented as a set of 3d points (x,y,z coordinates) contained in a np.array (dimension : (188706, 3))
[[168.998 167.681 0.] <-- point 1
[169.72 167.695 0.] <-- point 2
[170.44 167.629 0.] <-- point 3
...
[148.986 163.271 25.8] <-- point 188704
[148.594 163.634 25.8] <-- point 188705
[148.547 163.667 25.8]] <-- point 188706
Each pair of [[row x-1][row x]] represents a segment in 3D
[[168.998 167.681 0.] [169.72 167.695 0.] <-- SEGMENT 1 (points 1 & 2)
[169.72 167.695 0.] [170.44 167.629 0.] <-- SEGMENT 2 (points 2 & 3)
[170.44 167.629 0.] [171.149 167.483 0.] <-- SEGMENT 3 (points 3 & 4)
...
[149.328 162.853 25.8] [148.986 163.271 25.8] <-- SEGMENT 94351 (points 188703 & 188704)
[148.986 163.271 25.8] [148.594 163.634 25.8] <-- SEGMENT 94352 (points 18874 & 188705)
[148.594 163.634 25.8] [148.547 163.667 25.8]] <-- SEGMENT 94353 (points 188705 & 188706)
My goal is to measure the euclidean distance between each ordered points//pair of rows(= the length of each segments) so that I can detect where I need to add more points to represent a bit more the surface of my 3d model. In other words, if the segment length is above a threshold value (=0.5mm), I will have to discretize this particular segment with more points and to add those additional points in my points cloud.
I've found a way to measure the euclidean distance recurcively between each rows thanks to this code:
EuclidianDistance = np.linalg.norm(PtCloud[:-1] - PtCloud[1:],axis=1)
Which gives this result:
[0.72213572 0.72301867 0.72387637 ... 0.54008148 0.5342593 0.05742822]
And I found as well how to descretize a segment according to its vertices (extremities):
def AddEquidistantPoints(p1, p2, parts):
return np.stack((np.linspace(p1[0], p2[0], parts + 1), np.linspace(p1[1], p2[1], parts + 1)), axis=-1)
if EuclidianDistance > 0.5mm:
dist = AddEquidistantPoints(currentRow, previousRow, 10) #10 --> nb subdivisions
But my first issue is those euclidean distances have only to be computed on points where z coordinates are equals. Have I to split my array when z coordinate are not equals ?
With :
PtCloud = np.split(PtCloud, np.where(np.diff(PtCloud[:,2])!=0)[0]+1)
Which gives me a list of arrays so I suppose I will have unfortunately to use a for loop...
Here is the proper behavior represented with Excel:
And my second problem is related to the recurcive check and discretization step since I don't know how to implement it with this particular case. I wonder if there is a way to do it without any for loops.
So I would be very delighted if anyone could help me solve this challenge as I am currently "stuck".
It starts to be very challenging for me.
Thanks in advance,
Hervé
Just to share with you, I've just found a way to solve my problem. It is probably not the most efficient way to do that but it works.
import numpy as np
print("====================")
print("Initial Points Cloud")
print("====================")
POINTCLOUD = np.array([[168.998, 167.681, 0.],
[169.72, 167.695, 0.],
[170.44, 167.629, 0.],
[171.149, 167.483, 0.],
[150.149, 167.483, 4.2],
[160.149, 167.483, 4.2],
[159.149, 166.483, 4.2],
[152.149, 157.483, 7.],
[149.328, 162.853, 25.8],
[148.986, 163.271, 25.8],
[148.594, 163.634, 25.8],
[180.547, 170.667, 25.8],
[200.547, 190.667, 25.8]])
print(POINTCLOUD)
print("============================================")
print("Reshaped Point Cloud in the form of segments")
print("============================================")
a = np.column_stack((POINTCLOUD[:-1],POINTCLOUD[1:]))
print(a)
b = a.reshape((a.shape[0],2, 3))
#print(b)
print("")
print("*******************************")
print("Non filtered euclidean distance")
print("*******************************")
EuclidianDistance = np.transpose(np.linalg.norm(b[:,0] - b[:,1],axis=1))
print(EuclidianDistance)
print("")
print("****************")
print("Mask computation")
print("****************")
mask = np.transpose([a[:,2] == a[:,5]])
mask2 = [a[:,:] == a[:,:]]*np.transpose([a[:,2] == a[:,5]])
print("")
print(mask2)
print("")
print("***********************************")
print("1rst Filter applyed on points cloud")
print("***********************************")
# b = np.where(mask2,a,0)
# b = np.squeeze(b, axis=0)
b = np.squeeze(np.where(mask2,a,0), axis=0)
print(b)
print("")
b2 = b[np.squeeze(mask2,axis=0),...].reshape((np.sum(mask),b.shape[1]))
print(b2)
print("")
#print(b2.reshape(b2.shape[0],2, 3))
b = b2.reshape(b2.shape[0],2, 3)
print("")
print("***************************************")
print("FIRST EUCLIDEAN DISTANCE FILTERING STEP")
print("***************************************")
EuclidianDistance = np.linalg.norm(b[:,0] - b[:,1],axis=1)
print(EuclidianDistance)
print("")
print("***************************")
print("# THRESHOLD MASK GENERATION")
print("***************************")
threshold = 7
mask_threshold = np.transpose(EuclidianDistance>threshold)
print(mask_threshold)
print("")
print("**********************************")
print("# FINAL FILTERED ECLIDEAN DISTANCE")
print("**********************************")
EuclidianDistance = EuclidianDistance[np.squeeze(mask_threshold),...]
print(EuclidianDistance)
print("")
print("**********************")
print("SEGMENTS TO DISCRETIZE")
print("**********************")
SegmentToDiscretize = b[np.squeeze(mask_threshold),...]
print(SegmentToDiscretize)
print("")
print("******************************")
print("EQUIDISTANT POINTS COMPUTATION")
print("******************************")
nbsubdiv2 = np.transpose(np.ceil(np.array(np.divide(EuclidianDistance,0.7))).astype(int)).reshape((SegmentToDiscretize.shape[0],1))
print(nbsubdiv2)
print(nbsubdiv2.shape)
print(nbsubdiv2[1,0])
print(SegmentToDiscretize.shape)
print(SegmentToDiscretize[:,0])
nbsubdiv = [10,30,10]
addedpoint = np.linspace(SegmentToDiscretize[:,0],SegmentToDiscretize[:,1],nbsubdiv[0], dtype = np.float)
addedpoint = addedpoint.reshape((addedpoint.shape[0]*addedpoint.shape[1],3))
print(np.sort(addedpoint,axis=0))
print("")
print("***********************************")
print("UPDATED POINT CLOUD WITH NEW POINTS")
print("***********************************")
# duplicates are removed with the command np.unique
POINTCLOUD = np.unique(np.append(POINTCLOUD,addedpoint, axis=0),axis=0)
print(POINTCLOUD)
print("")
print("************************")
print("FINAL SORTED POINT CLOUD")
print("************************")
sortedPOINTCLOUD = POINTCLOUD[np.argsort(POINTCLOUD[:, 2])]
print(sortedPOINTCLOUD)
print("***************************")
Please do not hesitate to add your own proposal to improve it if you want. It will be very welcome !
This is my first question here, so point me out if I do something wrong.
I was implementing the equation to calculate an spherical angle (https://en.wikipedia.org/wiki/Solution_of_triangles#Solving_spherical_triangles). As math.acos and np.arccos are only defined for [-1,1] I hat to code a try/except statement. It worked well with math.acos, but I keep getting
RuntimeWarning: invalid value encountered in arccos
when I use np.arccos. As I want to do calculate the spherical angle for a very long file, I need np.arccos so I can get the benefits of vectorizarion. Any ideas on why this is happening or how I can imprrove the code?
Here is my attempt:
import numpy as np
import math
def spherical_to_cartesian(latr, lonr):
"""Convert a point given latitude and longitude in radians to
3D cartesian coordinates, assuming a sphere radius of one."""
return np.array((
np.cos(latr) * np.cos(lonr),
np.cos(latr) * np.sin(lonr),
np.sin(latr)
))
def angle_between_vectors(u, v):
'Return the angle between two vectors in any dimension space in radians'
return np.arccos(np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v)))
def angle_3pts(p1, p2, p3):
'Convert the points to numpy latitude/longitude radians space'
p1_rad = np.radians(np.array(p1))
p2_rad = np.radians(np.array(p2))
p3_rad = np.radians(np.array(p3))
'The points in 3D cartesian coordinates'
p1_cartesian = spherical_to_cartesian(*p1_rad)
p2_cartesian = spherical_to_cartesian(*p2_rad)
p3_cartesian = spherical_to_cartesian(*p3_rad)
'The angle between vectors'
ang_12 = angle_between_vectors(p1_cartesian, p2_cartesian)
ang_23 = angle_between_vectors(p2_cartesian, p3_cartesian)
ang_31 = angle_between_vectors(p3_cartesian, p1_cartesian)
print(ang_12, ang_23, ang_31)
'''Calculate spherical angle:
https://en.wikipedia.org/wiki/Solution_of_triangles#Solving_spherical_triangles'''
print((np.cos(ang_31) - np.cos(ang_12)*np.cos(ang_23))/(np.sin(ang_12)*np.sin(ang_23)))
try:
angle_3d = np.degrees(np.arccos((np.cos(ang_31) - np.cos(ang_12)*np.cos(ang_23))/(np.sin(ang_12)*np.sin(ang_23))))
except:
angle_3d = 180.
return angle_3d
point1 = (0, 0)
point2 = (0, 10)
point3 = (0, 20)
ang = angle_3pts(point1, point2, point3)
When I use math.acos inside the try equation it works well, but np.arccos yields the error
arccos is a ufunc, and takes where and out parameters:
In [226]: x = np.linspace(-2,2,20)
In [227]: np.arccos(x, where=(abs(x)<1), out=np.full_like(x,np.pi))
Out[227]:
array([3.14159265, 3.14159265, 3.14159265, 3.14159265, 3.14159265,
2.8157097 , 2.39918372, 2.12505816, 1.89208492, 1.67625485,
1.4653378 , 1.24950774, 1.01653449, 0.74240893, 0.32588296,
3.14159265, 3.14159265, 3.14159265, 3.14159265, 3.14159265])
This lets of specify which input values it should skip, and what to use instead.
Compared to a tried math version:
def foo(x):
try:
return math.acos(x)
except ValueError:
return math.pi
In [239]: np.array([foo(i) for i in x])
Out[239]:
array([3.14159265, 3.14159265, 3.14159265, 3.14159265, 3.14159265,
2.8157097 , 2.39918372, 2.12505816, 1.89208492, 1.67625485,
1.4653378 , 1.24950774, 1.01653449, 0.74240893, 0.32588296,
3.14159265, 3.14159265, 3.14159265, 3.14159265, 3.14159265])
I'm trying to test each red point for inclusion with the blue circle. However, the path for my circle has some strange values which is what I believe is causing the inclusion test to not work as intended.
The axis list in the code below represent the max & min for the longitude and latitude respectively. Given that the circle is plotted at the right location I expect its path to have vertices within that range which is not the case.
Where am I going wrong?
from matplotlib.patches import Ellipse
import matplotlib.path as mpltPath
axis = [4.7469287189121001, 5.0340994897259534, 52.282706941081258, 52.432452803031282]
unitX = (axis[1]-axis[0])/10
unitY = (axis[3]-axis[2])/10
fig, ax = plt.subplots(figsize=(8, 6))
for i, s in enumerate(housing_prices_shapes['2015']):
ax.plot(s[:,0], s[:,1], linewidth=0.5, c='0.5')
circle = Ellipse(housing_prices_shapes['2015'][0][0], width=unitX, height=unitY, edgecolor='b', facecolor='None')
ax.add_patch(circle)
listings_coordinates = airbnb_prices['2015'][["longitude", "latitude"]]
path_temp = circle.get_path()
transform = circle.get_transform()
new_path = transform.transform_path(path_temp)
path = mpltPath.Path(new_path.vertices)
flag = path.contains_points(listings_coordinates)
ax.scatter(listings_coordinates['longitude'].values, listings_coordinates['latitude'].values, c='r', s=0.5)
Each value used to create the circle prints as follow:
print(housing_prices_shapes['2015'][0][0], unitX, unitY)
[ 4.94147517 52.3670552 ] 0.028717077081385333 0.01497458619500236
The path variable which I expect to be in the same range as the longitude and latitude print as this, which is way off:
print(new_path.vertices)
array([[ 374.41773395, 221.41011283],
[ 380.33706714, 221.41011283],
[ 386.01475666, 223.12842659],
[ 390.2003573 , 226.18661544],
[ 394.38595794, 229.24480429],
[ 396.73773395, 233.39318067],
[ 396.73773395, 237.71811283],
[ 396.73773395, 242.04304498],
[ 394.38595794, 246.19142136],
[ 390.2003573 , 249.24961022],
[ 386.01475666, 252.30779907],
[ 380.33706714, 254.02611283],
[ 374.41773395, 254.02611283],
[ 368.49840076, 254.02611283],
[ 362.82071123, 252.30779907],
[ 358.63511059, 249.24961022],
[ 354.44950995, 246.19142136],
[ 352.09773395, 242.04304498],
[ 352.09773395, 237.71811283],
[ 352.09773395, 233.39318067],
[ 354.44950995, 229.24480429],
[ 358.63511059, 226.18661544],
[ 362.82071123, 223.12842659],
[ 368.49840076, 221.41011283],
[ 374.41773395, 221.41011283],
[ 374.41773395, 221.41011283]])
And of course no points are flagged as True:
print(any(flag))
False
As ImportanceOfBeingErnest noted in a comment, you shouldn't transform your ellipse path. Well, using the untransformed path wouldn't directly be useful either; you could probably make use of circle.get_verts().
But let me cut through your Gordian Knot: why not explicitly test for falling inside your ellipse? The equation of an ellipse with center (x0,y0) and semi-axes of length a and b is
(x-x0)^2/a^2 + (y-y0)^2/b^2 = 1
and it's really simple to see that the inside of the ellipse is then defined by the inequality
(x-x0)^2/a^2 + (y-y0)^2/b^2 < 1
(it's easy to see this for a circle, and you can think of an ellipse as a circle that went through a linear transform along one of its axes).
So use logical indexing to find which points are inside your ellipse! The only thing you need to watch out for is that the parameters passed to Ellipse are 2*a and 2*b:
points = airbnb_prices['2015'][['longitude', 'latitude']] # shape (N,2)
center = housing_prices_shapes['2015'][0][0] # shape (2,) broadcasts to (N,2)
a = unitX / 2 # scalar
b = unitY / 2 # scalar
# make use of broadcasting while we're at it
flag = ((points-center)**2 / np.array([a,b])**2).sum(axis=1) < 1
Now flag is a shape-(N,) logical array, i.e. the same shape and size as expected from your original call to contains_points.
I have the following problem. I have a numpy array of coordinates (entry 0 to 2) and want to define all the coordinates of small boxes between pairs of my coordiante list instead of creating a huge box around the minimum and maximum of all my coordinates in the list. The boxes should have a range of 5 around the coordinate pairs for example.
My list for example looks like:
[[ 24.313 294.679 1.5 1. 0. ]
[ 25.51 295.263 1.5 2. 0. ]
[ 26.743 294.526 1.5 3. 0. ]
...,
[ 30.362 307.242 10.779 95. 0. ]
[ 29.662 307.502 10.38 96. 0. ]
[ 29.947 308.99 11.147 97. 0. ]]
My first idea is to calculate the minumum and maximum of each pair and use itertools.product to create the coordinates for the small boxes. So i want to have a box around 24.313 294.679 1.5 and 25.51 295.263 1.5, next a box aorund 25.51 295.263 1.5 and 26.743 294.526 1.5 and so on. For better understanding, i want the coordinates like here, but in 3D of course:
And not like here:
Is there any easy numpy, scipy approach to do this?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
# create some data; in 2D so we can plot stuff
x = np.linspace(0, 2*np.pi, 10)
y = np.sin(x)
data = np.c_[x,y]
# --------------------------------------------------
# core bit: get boxes
# bboxes = np.array([data[:-1], np.diff(data, axis=0)]).transpose([1,2,0]) # shorter but with negative widths, etc
data_pairs = np.array([data[:-1], data[1:]])
minima = data_pairs.min(axis=0)
maxima = data_pairs.max(axis=0)
widths = maxima-minima
bboxes = np.array([minima, widths]).transpose(1,2,0)
# --------------------------------------------------
# plot
fig, ax = plt.subplots(1,1)
ax.plot(data[:,0], data[:,1], 'ko')
for bbox in bboxes:
patch = Rectangle(xy=bbox[:,0], width=bbox[0,1], height=bbox[1,1], linewidth=0., alpha=0.5)
ax.add_artist(patch)
plt.show()
with pads:
# padded boxes:
pad = 0.1
N, D = data.shape
correction = pad*np.ones((N-1,D))
padded = bboxes.copy()
padded[:,:,0] -= correction
padded[:,:,1] += 2*correction
fig, ax = plt.subplots(1,1)
ax.plot(data[:,0], data[:,1], 'ko')
for bbox in padded:
patch = Rectangle(xy=bbox[:,0], width=bbox[0,1], height=bbox[1,1], linewidth=0., alpha=0.5, facecolor='red')
ax.add_artist(patch)
ax.set_xlim(0-pad, 2*np.pi+pad)
ax.set_ylim(-1-pad, 1+pad)
plt.show()