Skip to content

Instantly share code, notes, and snippets.

@crackwitz
Last active July 28, 2021 15:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crackwitz/0d1e401b597b435bcc5e65349cbca870 to your computer and use it in GitHub Desktop.
Save crackwitz/0d1e401b597b435bcc5e65349cbca870 to your computer and use it in GitHub Desktop.
#!/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