Merge "Adding save and load functionality for camera intrinsic calibration"
diff --git a/y2020/vision/tools/python_code/BUILD b/y2020/vision/tools/python_code/BUILD
index a932886..1892a88 100644
--- a/y2020/vision/tools/python_code/BUILD
+++ b/y2020/vision/tools/python_code/BUILD
@@ -12,13 +12,9 @@
args = [
"sift_training_data.h",
],
- data = [
- ":test_images/train_loading_bay_blue.png",
- ":test_images/train_loading_bay_red.png",
- ":test_images/train_power_port_blue.png",
- ":test_images/train_power_port_red.png",
- ":test_images/train_power_port_red_webcam.png",
- ],
+ data = glob(["calib_files/*.txt"]) + glob([
+ "test_images/*.png",
+ ]),
default_python_version = "PY3",
srcs_version = "PY2AND3",
deps = [
@@ -64,9 +60,9 @@
"sift_training_data_test.h",
"test",
],
- data = [
- ":test_images/train_power_port_red.png",
- ],
+ data = glob(["calib_files/*.txt"]) + glob([
+ "test_images/*.png",
+ ]),
default_python_version = "PY3",
main = "load_sift_training.py",
srcs_version = "PY2AND3",
diff --git a/y2020/vision/tools/python_code/calib_files/cam-calib-int_pi-7971-3_Feb-13-2020-00-00-00.txt b/y2020/vision/tools/python_code/calib_files/cam-calib-int_pi-7971-3_Feb-13-2020-00-00-00.txt
new file mode 100644
index 0000000..8c1efbb
--- /dev/null
+++ b/y2020/vision/tools/python_code/calib_files/cam-calib-int_pi-7971-3_Feb-13-2020-00-00-00.txt
@@ -0,0 +1 @@
+{"hostname": "pi-7971-3", "node_name": "pi3", "team_number": 7971, "timestamp": "Feb-13-2020-00-00-00", "camera_matrix": [[388.79947319784713, 0.0, 345.0976031055917], [0.0, 388.539148344188, 265.2780372766764], [0.0, 0.0, 1.0]], "dist_coeffs": [[0.13939139612079282, -0.24345067782097646, -0.0004228219772016648, -0.0004552350162154737, 0.08966339831250879]]}
diff --git a/y2020/vision/tools/python_code/calibrate_intrinsics.py b/y2020/vision/tools/python_code/calibrate_intrinsics.py
index df1b328..828a9a9 100644
--- a/y2020/vision/tools/python_code/calibrate_intrinsics.py
+++ b/y2020/vision/tools/python_code/calibrate_intrinsics.py
@@ -1,23 +1,63 @@
-import time
import cv2
-import cv2.aruco as A
+import cv2.aruco
+import datetime
+import glog
+import json
+from json import JSONEncoder
import numpy as np
+import os
+import time
-dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)
-board = cv2.aruco.CharucoBoard_create(11, 8, .015, .011, dictionary)
-img = board.draw((200 * 11, 200 * 8))
+
+# From: https://pynative.com/python-serialize-numpy-ndarray-into-json/
+class NumpyArrayEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, np.integer):
+ return int(obj)
+ elif isinstance(obj, np.floating):
+ return float(obj)
+ elif isinstance(obj, np.ndarray):
+ return obj.tolist()
+ else:
+ return super(NumpyArrayEncoder, self).default(obj)
+
+
+def get_robot_info(hostname):
+ hostname_split = hostname[1].split("-")
+ if hostname_split[0] != "pi":
+ print("ERROR: expected hostname to start with pi!")
+ quit()
+
+ team_number = int(hostname_split[1])
+ node_name = hostname_split[0] + hostname_split[2]
+ return node_name, team_number
+
+
+USE_LARGE_BOARD = False
+
+if USE_LARGE_BOARD:
+ dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_5X5_100)
+ # Need to measure what the second size parameter is (markerLength)
+ board = cv2.aruco.CharucoBoard_create(12, 9, .06, .045, dictionary)
+ img = board.draw((200 * 12, 200 * 9))
+else:
+ dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)
+ board = cv2.aruco.CharucoBoard_create(11, 8, .015, .011, dictionary)
+ img = board.draw((200 * 11, 200 * 8))
#Dump the calibration board to a file
cv2.imwrite('charuco.png', img)
#Start capturing images for calibration
-CAMERA_INDEX = 2
+CAMERA_INDEX = 0 # Capture from /dev/videoX, where X=CAMERA_INDEX
cap = cv2.VideoCapture(CAMERA_INDEX)
allCorners = []
allIds = []
capture_count = 0
-while (capture_count < 50):
+MIN_IMAGES = 50
+
+while (capture_count < MIN_IMAGES):
ret, frame = cap.read()
assert ret, "Unable to get image from the camera at /dev/video%d" % CAMERA_INDEX
@@ -25,45 +65,69 @@
res = cv2.aruco.detectMarkers(gray, dictionary)
aruco_detect_image = frame.copy()
- if len(res[0]) > 0:
+ if len(res[0]) > 0 and len(res[1]) > 0:
cv2.aruco.drawDetectedMarkers(aruco_detect_image, res[0], res[1])
+ # Display every image to let user trigger capture
cv2.imshow('frame', aruco_detect_image)
keystroke = cv2.waitKey(1)
+
if keystroke & 0xFF == ord('q'):
break
elif keystroke & 0xFF == ord('c'):
- print("Res:", len(res[0]), res[1].shape)
+ glog.info("Asked to capture image")
+ if len(res[0]) == 0 or len(res[1]) == 0:
+ # Can't use this image
+ continue
+
res2 = cv2.aruco.interpolateCornersCharuco(res[0], res[1], gray, board)
if res2[1] is not None and res2[2] is not None and len(res2[1]) > 3:
capture_count += 1
charuco_detect_image = frame.copy()
allCorners.append(res2[1])
allIds.append(res2[2])
- print("Res2: ", res2[1].shape, res2[2].shape)
+ glog.info("Capturing image #%d" % capture_count)
cv2.aruco.drawDetectedCornersCharuco(charuco_detect_image, res2[1],
res2[2])
+
cv2.imshow('frame', charuco_detect_image)
cv2.waitKey(1000)
- # TODO: Should log image to disk
- print("Captured image #", capture_count)
-
-imsize = gray.shape
+ # TODO<Jim>: Should log image to disk
#Calibration fails for lots of reasons. Release the video if we do
try:
+ imsize = gray.shape
cal = cv2.aruco.calibrateCameraCharuco(allCorners, allIds, board, imsize,
None, None)
- #print("Calibration is:\n", cal)
- print("Reproduction error:", cal[0])
+ glog.info("Calibration is:\n", cal)
+ glog.info("Reproduction error:", cal[0])
if (cal[0] > 1.0):
- print("REPRODUCTION ERROR NOT GOOD")
- # TODO<jim>: Need to save these out in format that can be used elsewhere
- print("Calibration matrix:\n", cal[1])
- print("Distortion Coefficients:\n", cal[2])
+ glog.error("REPRODUCTION ERROR NOT GOOD")
+ glog.info("Calibration matrix:\n", cal[1])
+ glog.info("Distortion Coefficients:\n", cal[2])
except:
- print("Calibration failed")
+ glog.error("Calibration failed")
cap.release()
+ quit()
+
+hostname = os.uname()[1]
+date_str = datetime.datetime.today().strftime("%Y-%m-%d-%H-%M-%S")
+node_name, team_number = get_robot_info(hostname)
+numpyData = {
+ "hostname": hostname,
+ "node_name": node_name,
+ "team_number": team_number,
+ "timestamp": date_str,
+ "camera_matrix": cal[1],
+ "dist_coeffs": cal[2]
+}
+encodedNumpyData = json.dumps(
+ numpyData, cls=NumpyArrayEncoder) # use dump() to write array into file
+
+# Write out the data
+calib_file = open("cam_calib_%s_%s.txt" % (hostname, date_str), "w")
+calib_file.write(encodedNumpyData)
+calib_file.close()
cap.release()
cv2.destroyAllWindows()
diff --git a/y2020/vision/tools/python_code/camera_definition.py b/y2020/vision/tools/python_code/camera_definition.py
index 0667b16..4d8929a 100644
--- a/y2020/vision/tools/python_code/camera_definition.py
+++ b/y2020/vision/tools/python_code/camera_definition.py
@@ -1,6 +1,20 @@
import copy
+import glog
+import json
import math
import numpy as np
+import os
+
+glog.setLevel("WARN")
+USE_BAZEL = True
+
+
+def bazel_name_fix(filename):
+ ret_name = filename
+ if USE_BAZEL:
+ ret_name = 'org_frc971/y2020/vision/tools/python_code/' + filename
+
+ return ret_name
class CameraIntrinsics:
@@ -53,9 +67,41 @@
camera_list = []
+# TODO<Jim>: Should probably make this a dict to make replacing easier
for team_number in (971, 7971, 8971, 9971):
- for node_name in ("pi0", "pi1", "pi2", "pi3", "pi4"):
+ for node_name in ("pi0", "pi1", "pi2", "pi3", "pi4", "pi5"):
camera_base = copy.deepcopy(web_cam_params)
camera_base.node_name = node_name
camera_base.team_number = team_number
camera_list.append(camera_base)
+
+dir_name = ('calib_files')
+
+if USE_BAZEL:
+ from bazel_tools.tools.python.runfiles import runfiles
+ r = runfiles.Create()
+ dir_name = r.Rlocation(bazel_name_fix('calib_files'))
+
+for filename in os.listdir(dir_name):
+ if "cam-calib-int" in filename and filename.endswith(".txt"):
+ # Extract intrinsics from file
+ fn_split = filename.split("_")
+ hostname_split = fn_split[1].split("-")
+ if hostname_split[0] == "pi":
+ team_number = int(hostname_split[1])
+ node_name = hostname_split[0] + hostname_split[2]
+
+ calib_file = open(dir_name + "/" + filename, 'r')
+ calib_dict = json.loads(calib_file.read())
+ hostname = np.asarray(calib_dict["hostname"])
+ camera_matrix = np.asarray(calib_dict["camera_matrix"])
+ dist_coeffs = np.asarray(calib_dict["dist_coeffs"])
+
+ # Look for match, and replace camera_intrinsics
+ for camera_calib in camera_list:
+ if camera_calib.node_name == node_name and camera_calib.team_number == team_number:
+ glog.info("Found calib for %s, team #%d" % (node_name,
+ team_number))
+ camera_calib.camera_int.camera_matrix = copy.copy(
+ camera_matrix)
+ camera_calib.camera_int.dist_coeffs = copy.copy(dist_coeffs)