From 5b4f58de47294df99351b947f17c2233b60a50a7 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:14:08 -0600 Subject: [PATCH 1/6] ensure the top/bottom black corner is used as origin for the findchessboardcornerssb method test cases that were consistent between methods: # '1dfd1ef5-7724-48a5-b3d5-8809b88764e1', # horiz black upper left # '0a60a367-e5a3-4f34-b052-a5a11ab84195', # horiz white upper left # '8784cae6-0508-4698-bac4-b9c8f2d72f41', # vert white upper left # 'a9aa74d0-34dc-462b-883d-bf6b35f19509' # vert black upper left --- Examples/reprocessSessions.py | 14 ++++-- main.py | 6 ++- utilsChecker.py | 88 +++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/Examples/reprocessSessions.py b/Examples/reprocessSessions.py index 37330ebd..6a024425 100644 --- a/Examples/reprocessSessions.py +++ b/Examples/reprocessSessions.py @@ -55,7 +55,13 @@ # Enter the identifier(s) of the session(s) you want to reprocess. This is a list of one # or more session identifiers. The identifier is found as the 36-character string at the # end of the session url: app.opencap.ai/session/ -session_ids = ['23d52d41-69fe-47cf-8b60-838e4268dd50'] +session_ids = ['0a9d564d-8fe3-4d9d-9946-d36529ffb122', # degenerate at conf + '1dfd1ef5-7724-48a5-b3d5-8809b88764e1', # horiz black upper left + '0a60a367-e5a3-4f34-b052-a5a11ab84195', # horiz white upper left + '8784cae6-0508-4698-bac4-b9c8f2d72f41', # vert white upper left + 'a9aa74d0-34dc-462b-883d-bf6b35f19509' # vert black upper left + ] + # Select which trials to reprocess. You can reprocess all trials in the session # by entering None in all fields below. The correct calibration and static @@ -66,9 +72,9 @@ # select specific trials. Only one trial (str) is allowed for calib_id and # static_id. A list of strings is allowed for dynamic_trialNames. -calib_id = [] # None (auto-selected trial), [] (skip), or string of specific trial_id +calib_id = None # None (auto-selected trial), [] (skip), or string of specific trial_id static_id = [] # None (auto-selected trial), [] (skip), or string of specific trial_id -dynamic_trialNames = None # None (all dynamic trials), [] (skip), or list of trial names +dynamic_trialNames = [] # None (all dynamic trials), [] (skip), or list of trial names # Select which pose estimation model to use; options are 'OpenPose' and 'hrnet'. # If the same pose estimation model was used when collecting data with the web @@ -111,3 +117,5 @@ poseDetector=poseDetector, resolutionPoseDetection=resolutionPoseDetection, deleteLocalFolder=deleteLocalFolder) + +test=1 \ No newline at end of file diff --git a/main.py b/main.py index bff335b0..42e6af0f 100644 --- a/main.py +++ b/main.py @@ -44,7 +44,8 @@ def main(sessionName, trialName, trial_id, cameras_to_use=['all'], dataDir=None, overwriteAugmenterModel=False, filter_frequency='default', overwriteFilterFrequency=False, scaling_setup='upright_standing_pose', overwriteScalingSetup=False, - overwriteCamerasToUse=False, syncVer=None,): + overwriteCamerasToUse=False, syncVer=None, + useLidarIntrinsics=False,): # %% High-level settings. # Camera calibration. @@ -220,7 +221,8 @@ def main(sessionName, trialName, trial_id, cameras_to_use=['all'], camDir = cameraDirectories[camName] lidarIntrinsicPath = os.path.join(camDir, 'InputMedia', trialName, 'camera_matrix.csv') - hasLidarIntrinsics = os.path.exists(lidarIntrinsicPath) + hasLidarIntrinsics = ( + useLidarIntrinsics and os.path.exists(lidarIntrinsicPath)) # Intrinsics ###################################################### # Intrinsics and extrinsics already exist for this session. if os.path.exists( diff --git a/utilsChecker.py b/utilsChecker.py index 00014476..4dd80579 100644 --- a/utilsChecker.py +++ b/utilsChecker.py @@ -19,6 +19,7 @@ from scipy.interpolate import pchip_interpolate from scipy.spatial.transform import Rotation from itertools import combinations +from numpy.lib.stride_tricks import sliding_window_view import copy from utilsCameraPy3 import Camera, nview_linear_triangulations from utils import getOpenPoseMarkerNames, getOpenPoseFaceMarkers @@ -273,6 +274,80 @@ def generate3Dgrid(CheckerBoardParams): return objectp3d + +def warpChessboardToCanonicalView(img, corners, pattern, squareResolution=1): + width, height = pattern[:2] + canonicalCorners = np.array([ + [0.5, 0.5], + [width - 0.5, 0.5], + [width - 0.5, height - 0.5], + [0.5, height - 0.5] + ]) + canonicalCorners = (canonicalCorners + 0.5) * squareResolution - 0.5 + + imageCorners = corners[[0, + width - 1, + (height - 1) * width + width - 1, + (height - 1) * width]].reshape(-1, 2) + homography, _ = cv2.findHomography(imageCorners, + canonicalCorners.reshape(-1, 2)) + if homography is None: + return None + + return cv2.warpPerspective( + img, + homography, + ((width + 1) * squareResolution, (height + 1) * squareResolution), + flags=cv2.INTER_NEAREST) + + +def needsCornerOrderFlip(canonicalImage, squareResolution=1): + if canonicalImage.ndim == 3: + normalizedImage = (canonicalImage / 255.0).mean(-1) + else: + normalizedImage = canonicalImage / 255.0 + + normalizedImage = sliding_window_view( + normalizedImage, (squareResolution, squareResolution)).mean((-1, -2)) + + def signOfDeterminant(i, j): + return np.sign(normalizedImage[i, j] * normalizedImage[i + 1, j + 1] - + normalizedImage[i, j + 1] * normalizedImage[i + 1, j]) + + height, width = normalizedImage.shape[:2] + cornerSigns = ( + signOfDeterminant(0, 0), + signOfDeterminant(0, width - 2), + signOfDeterminant(height - 2, width - 2), + signOfDeterminant(height - 2, 0)) + + if sum(cornerSigns) != 0: + return None, "Pattern not identified correctly, or not an asymmetric pattern" + + return cornerSigns[0] > 0, None + + +def ensureCornerOrdering(img, corners, pattern, squareResolution=1): + # Requires an asymmetric pattern, i.e. exactly one pattern dimension is odd. + if (pattern[0] % 2 == 0) == (pattern[1] % 2 == 0): + return corners, False, "Cannot ensure SB checkerboard ordering without an asymmetric pattern" + + canonicalImage = warpChessboardToCanonicalView( + img, corners, pattern, squareResolution=squareResolution) + if canonicalImage is None: + return corners, False, "Could not compute checkerboard homography" + + needsFlip, errorMessage = needsCornerOrderFlip( + canonicalImage, squareResolution=squareResolution) + if errorMessage is not None: + return corners, False, errorMessage + + if needsFlip: + print('flipped corners for extrinsics') + corners = corners[::-1] + + return corners, True, None + # %% def saveCameraParameters(filename,CameraParams): if not os.path.exists(os.path.dirname(filename)): @@ -433,14 +508,21 @@ def calcExtrinsics(imageFileName, CameraParams, CheckerBoardParams, # certain lighting/contrast conditions where adaptive thresholding struggles). # Using ACCURACY|LARGER without EXHAUSTIVE to keep the fallback reasonably fast. corners2_from_sb = False + ret=False if not ret: ret_sb, corners_sb, _ = cv2.findChessboardCornersSBWithMeta( grayColor, CheckerBoardParams['dimensions'], - cv2.CALIB_CB_ACCURACY | cv2.CALIB_CB_LARGER) + cv2.CALIB_CB_ACCURACY | cv2.CALIB_CB_LARGER) if ret_sb: ret = True - corners = corners_sb - corners2_from_sb = True + corners, orderingSuccess, orderingError = ensureCornerOrdering( + grayColor, corners_sb, CheckerBoardParams['dimensions'], + squareResolution=2) + if orderingSuccess: + corners2_from_sb = True + else: + print('Rejected SB checkerboard detection: ' + orderingError) + ret = False # If desired number of corners can be detected then, # refine the pixel coordinates and display From b381a406f416f208b14130707b62e475615e8659 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:42:30 -0600 Subject: [PATCH 2/6] revert --- utilsChecker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/utilsChecker.py b/utilsChecker.py index 4dd80579..c976df38 100644 --- a/utilsChecker.py +++ b/utilsChecker.py @@ -508,7 +508,6 @@ def calcExtrinsics(imageFileName, CameraParams, CheckerBoardParams, # certain lighting/contrast conditions where adaptive thresholding struggles). # Using ACCURACY|LARGER without EXHAUSTIVE to keep the fallback reasonably fast. corners2_from_sb = False - ret=False if not ret: ret_sb, corners_sb, _ = cv2.findChessboardCornersSBWithMeta( grayColor, CheckerBoardParams['dimensions'], From c0031b645d90ecf6fc2abc305f1cdce7e2b4d311 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:07:46 -0600 Subject: [PATCH 3/6] review --- Examples/reprocessSessions.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Examples/reprocessSessions.py b/Examples/reprocessSessions.py index 6a024425..dd4eefa6 100644 --- a/Examples/reprocessSessions.py +++ b/Examples/reprocessSessions.py @@ -55,12 +55,7 @@ # Enter the identifier(s) of the session(s) you want to reprocess. This is a list of one # or more session identifiers. The identifier is found as the 36-character string at the # end of the session url: app.opencap.ai/session/ -session_ids = ['0a9d564d-8fe3-4d9d-9946-d36529ffb122', # degenerate at conf - '1dfd1ef5-7724-48a5-b3d5-8809b88764e1', # horiz black upper left - '0a60a367-e5a3-4f34-b052-a5a11ab84195', # horiz white upper left - '8784cae6-0508-4698-bac4-b9c8f2d72f41', # vert white upper left - 'a9aa74d0-34dc-462b-883d-bf6b35f19509' # vert black upper left - ] +session_ids = ['23d52d41-69fe-47cf-8b60-838e4268dd50'] # Select which trials to reprocess. You can reprocess all trials in the session From 61a34a4a634e4b1463a1bc6faa34985edb108e3a Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:29:10 -0600 Subject: [PATCH 4/6] clean --- Examples/reprocessSessions.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Examples/reprocessSessions.py b/Examples/reprocessSessions.py index dd4eefa6..a336bfb3 100644 --- a/Examples/reprocessSessions.py +++ b/Examples/reprocessSessions.py @@ -57,7 +57,6 @@ # end of the session url: app.opencap.ai/session/ session_ids = ['23d52d41-69fe-47cf-8b60-838e4268dd50'] - # Select which trials to reprocess. You can reprocess all trials in the session # by entering None in all fields below. The correct calibration and static # trials will be automatically selected if None, and all dynamic trials will be @@ -67,9 +66,9 @@ # select specific trials. Only one trial (str) is allowed for calib_id and # static_id. A list of strings is allowed for dynamic_trialNames. -calib_id = None # None (auto-selected trial), [] (skip), or string of specific trial_id +calib_id = [] # None (auto-selected trial), [] (skip), or string of specific trial_id static_id = [] # None (auto-selected trial), [] (skip), or string of specific trial_id -dynamic_trialNames = [] # None (all dynamic trials), [] (skip), or list of trial names +dynamic_trialNames = None # None (all dynamic trials), [] (skip), or list of trial names # Select which pose estimation model to use; options are 'OpenPose' and 'hrnet'. # If the same pose estimation model was used when collecting data with the web @@ -111,6 +110,4 @@ batchReprocess(session_ids,calib_id,static_id,dynamic_trialNames, poseDetector=poseDetector, resolutionPoseDetection=resolutionPoseDetection, - deleteLocalFolder=deleteLocalFolder) - -test=1 \ No newline at end of file + deleteLocalFolder=deleteLocalFolder) \ No newline at end of file From 92c68abf4d755b31724c4def7d2e242d5869d743 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:41:42 -0600 Subject: [PATCH 5/6] citation --- utilsChecker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utilsChecker.py b/utilsChecker.py index c976df38..f3178c2a 100644 --- a/utilsChecker.py +++ b/utilsChecker.py @@ -274,7 +274,8 @@ def generate3Dgrid(CheckerBoardParams): return objectp3d - +# codex implementation of this https://github.com/opencv/opencv/issues/22083#issuecomment-2354470395 +# to identify where the black corner is on an asymmetrical chessboard def warpChessboardToCanonicalView(img, corners, pattern, squareResolution=1): width, height = pattern[:2] canonicalCorners = np.array([ From 4a6dada738f18dd66237d4a1f42c2081e1c83152 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:43:11 -0600 Subject: [PATCH 6/6] add exhaustive so we can test all at once --- utilsChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilsChecker.py b/utilsChecker.py index f3178c2a..15a3185c 100644 --- a/utilsChecker.py +++ b/utilsChecker.py @@ -512,7 +512,7 @@ def calcExtrinsics(imageFileName, CameraParams, CheckerBoardParams, if not ret: ret_sb, corners_sb, _ = cv2.findChessboardCornersSBWithMeta( grayColor, CheckerBoardParams['dimensions'], - cv2.CALIB_CB_ACCURACY | cv2.CALIB_CB_LARGER) + cv2.CALIB_CB_ACCURACY | cv2.CALIB_CB_LARGER | cv2.CALIB_CB_EXHAUSTIVE) if ret_sb: ret = True corners, orderingSuccess, orderingError = ensureCornerOrdering(