Last active
July 28, 2021 15:45
-
-
Save crackwitz/0d1e401b597b435bcc5e65349cbca870 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import os | |
import sys | |
import numpy as np | |
import cv2 as cv | |
np.set_printoptions(suppress=True) | |
# https://stackoverflow.com/questions/68541509/evaluate-the-correctness-of-in-extrinsic-params-resulting-from-camera-calibratio | |
# https://www.nikonians.org/reviews/fov-tables | |
def ipt(val, shift=0): | |
if isinstance(val, np.ndarray): | |
val = np.squeeze(val) | |
val = np.int32(np.round(val * 2**shift)) | |
if len(val.shape) <= 1: | |
return tuple(val) | |
else: | |
return val | |
else: | |
return int(round(val * 2**shift)) | |
# https://en.wikipedia.org/wiki/Tennis_court#Dimensions | |
ft = .3048 # meters | |
court_width = 36 * ft # meters | |
court_length = 2 * 39 * ft # meters | |
objPts = np.float32([ | |
# center of court is 0,0,0 | |
# values in ft, then converted to meters, for convenience | |
# outer points | |
[-18.0, +39.0, 0], # far left | |
[+18.0, +39.0, 0], # far right | |
[+18.0, -39.0, 0], # near right | |
[-18.0, -39.0, 0], # near left | |
# inner points | |
[-13.5, +21.0, 0], # far left | |
[+13.5, +21.0, 0], # far right | |
[+13.5, -21.0, 0], # near right | |
[-13.5, -21.0, 0], # near left | |
]).reshape((-1, 1, 3)) * ft | |
lines = [ | |
(0,1), (1,2), (2,3), (3,0), | |
(4,5), (5,6), (6,7), (7,4) | |
] | |
# points centered on the outside lines | |
# width, height = 1366, 768 | |
# imgPts = np.float32([ | |
# [ 346, 245], # far left | |
# [ 988, 244], # far right | |
# [1188, 607], # near right | |
# [ 142, 611], # near left | |
# ]) | |
# points on the outsides of the outside lines (one variant) | |
# width, height = 1366, 768 | |
# imgPts = np.float32([ | |
# [ 345, 244], # far left | |
# [ 989, 243], # far right | |
# [1192, 609], # near right | |
# [ 139, 612], # near left | |
# ]) | |
# points on the outsides of the outside lines (other variant) | |
# width, height = 1366, 768 | |
# imgPts = np.float32([ | |
# [ 344, 244], # far left | |
# [ 989, 243], # far right | |
# [1192, 609], # near right | |
# [ 138, 613], # near left | |
# ]) | |
width, height = 1920, 1080 | |
imgPts = np.float32([ | |
# outer: far left, far right, near right, near left | |
# inner: ... | |
[ 574.46875, 301. ], | |
[1335.5312 , 300.71875], | |
[1569.1562 , 859.46875], | |
[ 359.71875, 859.40625], | |
[ 647.90625, 392. ], | |
[1265.25 , 391. ], | |
[1354.125 , 676.90625], | |
[ 567.8125 , 677.9375 ] | |
]).reshape((-1, 1, 2)) | |
mag_mouse = 32 | |
mag_callout = 16 | |
callout_size = 2*384 | |
#im = cv.imread("vxUZD.jpg") | |
im = cv.imread("vbfei.jpg") | |
assert (height, width) == im.shape[:2] | |
print(f"image size:\n\t{width} x {height}") | |
def draw_court(im, imgPts, color=(0,0,255)): | |
for i,j in lines: | |
cv.line(im, | |
pt1=ipt(imgPts[i], shift=4), | |
pt2=ipt(imgPts[j], shift=4), | |
color=color, | |
thickness=1, | |
lineType=cv.LINE_AA, | |
shift=4 | |
) | |
def translate(dx=0, dy=0): | |
res = np.eye(3) | |
res[0:2,2] = (dx, dy) | |
return res | |
def scale(s=1, sx=1, sy=1): | |
res = np.eye(3) | |
res[0,0] = s * sx | |
res[1,1] = s * sy | |
return res | |
def draw(callout=None): | |
canvas = im.copy() | |
draw_court(canvas, imgPts, color=(255,0,0)) | |
if callout is None: | |
pass | |
else: | |
(x,y) = callout.flat | |
H = np.eye(3) | |
H = H @ translate(dx=callout_size/2, dy=callout_size/2) # last | |
H = H @ scale(s=mag_callout) # then | |
H = H @ translate(dx=-x, dy=-y) # first | |
callout_canvas = cv.warpAffine( | |
canvas, H[0:2], (callout_size, callout_size), | |
flags=cv.INTER_NEAREST, | |
borderMode=cv.BORDER_CONSTANT) | |
cv.imshow("callout", callout_canvas) | |
cv.circle(canvas, ipt(callout, shift=4), radius=ipt(15, shift=4), color=(0,255,0), thickness=5, lineType=cv.LINE_AA, shift=4) | |
draw_court(canvas, imgPts2, color=(0,0,255)) | |
cv.imshow("court", canvas) | |
def process(): | |
global C, fx, fd | |
global C2 | |
global rvec, tvec | |
global imgPts2 | |
C = cv.initCameraMatrix2D([objPts], [imgPts], (width, height)) | |
(rv, C2, distCoeffs, rvecs, tvecs) = cv.calibrateCamera( | |
[objPts], [imgPts], | |
imageSize=(1920, 1080), | |
cameraMatrix=C.copy(), | |
distCoeffs=np.float32([0,0,0,0,0]), | |
flags=0 | |
| cv.CALIB_FIX_ASPECT_RATIO | |
| cv.CALIB_FIX_PRINCIPAL_POINT | |
| cv.CALIB_ZERO_TANGENT_DIST | |
| cv.CALIB_FIX_K1 | |
| cv.CALIB_FIX_K2 | |
| cv.CALIB_FIX_K3 | |
| cv.CALIB_FIX_K4 | |
| cv.CALIB_FIX_K5 | |
) | |
# C[0,0] = C[1,1] = C2[0,0] | |
# C[1,2] = C2[1,2] | |
# C[0,2] = C2[0,2] | |
C = C2 | |
#print(np.linalg.norm(C-C2)) | |
print("distortion coefficients:") | |
print(distCoeffs.T) | |
print("camera matrix:") | |
print(C) | |
fx = C[0,0] | |
# fx * tan(hfov/2) == width/2 | |
hfov = np.arctan(width/2 / fx) * 2 | |
print(f"horizontal FoV:\n\t{hfov / np.pi * 180:.2f} °") | |
# x? mm focal length -> 36 mm horizontal (24 vertical)? | |
fd = 36 / (np.tan(hfov/2) * 2) | |
print(f"focal length (35mm equivalent):\n\t{fd:.2f} mm") | |
(rv, rvec, tvec) = cv.solvePnP(objPts, imgPts, C, distCoeffs=None) | |
print("tvec [m]:") | |
print(tvec) | |
(imgPts2, jac) = cv.projectPoints( | |
objectPoints=objPts, | |
rvec=rvec, | |
tvec=tvec, | |
cameraMatrix=C, | |
distCoeffs=None) | |
mouse_selection_index = None | |
mouse_down_cursor_offset = None | |
mouse_down_point = None | |
mouse_down_orig_data = None | |
def onmouse(event, x, y, flags, userdata=None): | |
global mouse_down_cursor_offset | |
global mouse_selection_index | |
global mouse_down_point | |
global mouse_down_orig_data | |
do_update = False | |
if event == cv.EVENT_LBUTTONDOWN: | |
# find closest point | |
dists = np.linalg.norm(imgPts - (x,y), axis=2) | |
i = np.argmin(dists) | |
mouse_selection_index = i | |
mouse_down_cursor_offset = (x,y) - imgPts[i] | |
mouse_down_point = np.float32([x,y]) | |
mouse_down_orig_data = imgPts[i].copy() | |
if (event == cv.EVENT_LBUTTONUP) or (flags & cv.EVENT_FLAG_LBUTTON): | |
if mouse_selection_index is not None: | |
i = mouse_selection_index | |
#imgPts[i] = (x,y) - mouse_down_cursor_offset | |
distance = (x,y) - mouse_down_point | |
imgPts[i] = mouse_down_orig_data + distance / mag_mouse # magnification | |
do_update = True | |
if event == cv.EVENT_LBUTTONUP: | |
mouse_selection_index = None | |
mouse_down_orig_data = None | |
if do_update: | |
process() | |
draw(callout=mouse_down_orig_data) | |
cv.namedWindow("court", cv.WINDOW_OPENGL) | |
cv.resizeWindow("court", width, height) | |
cv.setMouseCallback("court", onmouse) | |
process() | |
draw() | |
while True: | |
key = cv.waitKey(-1) | |
if key == -1: continue | |
if key in (13, 27): break | |
cv.destroyAllWindows() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment