Last active
September 17, 2024 08:06
-
-
Save cheind/423642ec9995a8ecfd6e716642010a1c to your computer and use it in GitHub Desktop.
Ensures a consistent ordering of corners returned by cv2.findChessboardCornersSB for asymmetrical chessboards.
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
""" | |
Ensures a consistent ordering of corners returned by cv2.findChessboardCornersSB | |
for asymmetrical chessboards. | |
For patterns with dims i, j where i > j and i is odd the script ensures the corner | |
order to start at a black square running along the positive i axis. | |
The detected corners are used to create a canonical chessboard view. This | |
view is analysed by looking at the 2x2 blocks associated with each corner. | |
A characteristic value is computed from each 2x2 block and if the series | |
of values deviates from a desired ordering, a reversal of detected corners | |
is applied. | |
See: | |
https://github.com/opencv/opencv/issues/22083#issuecomment-2354470395 | |
Author: | |
github.com/cheind, 2024 | |
""" | |
import cv2 | |
import numpy as np | |
from numpy.lib.stride_tricks import sliding_window_view | |
import warnings | |
def _warp(img: np.ndarray, corners: np.ndarray, pattern: tuple[int, int], r: int = 1): | |
"""Transforms chessboard into a canonical view with square resolution `r`. | |
Params: | |
img: original image | |
corners: (N,2) detected images corners | |
pattern: Chessboard pattern layout | |
r: resolution per square in canonical image | |
Returns: | |
cimg: canoncical chessboard image including outer squares. | |
""" | |
w, h = pattern[:2] | |
obj = np.array([[0.5, 0.5], [w - 0.5, 0.5], [w - 0.5, h - 0.5], [0.5, h - 0.5]]) | |
obj = (obj + 0.5) * r - 0.5 | |
H, _ = cv2.findHomography( | |
corners[[0, w - 1, (h - 1) * w + w - 1, (h - 1) * w]].reshape(-1, 2), | |
obj.reshape(-1, 2), | |
) | |
img = cv2.warpPerspective( | |
img, H, ((w + 1) * r, (h + 1) * r), flags=cv2.INTER_NEAREST | |
) | |
return img | |
def _needs_flip(wimg: np.ndarray, r: int = 1): | |
"""Determines if corners need to be reversed. | |
Looks at 2x2 squares corresponding to each corner. | |
Computes from each 2x2 block a value corresponding to | |
the 2d determinant of the values interpreted as matrix. | |
From the sign of the determinant determines if a reversal | |
is necessary. | |
""" | |
n = (wimg / 255.0).mean(-1) | |
n = sliding_window_view(n, (r, r)).mean((-1, -2)) | |
def sign_of_det(i, j): | |
return np.sign(n[i, j] * n[i + 1, j + 1] - n[i, j + 1] * n[i + 1, j]) | |
h, w = n.shape[:2] | |
a, b, c, d = ( | |
sign_of_det(0, 0), | |
sign_of_det(0, w - 2), | |
sign_of_det(h - 2, w - 2), | |
sign_of_det(h - 2, 0), | |
) | |
if (a + b + c + d) != 0: | |
warnings.warn("Pattern not identified correctly, or not an asymmetric pattern") | |
return a > 0 | |
def ensure_corner_ordering( | |
img: np.ndarray, corners: np.ndarray, pattern: tuple[int, int], r: int = 1 | |
): | |
"""Reverses image corners for a consistent corner ordering. | |
Requires an assymetrical pattern (i.e one where exactly one pattern | |
dimension is odd). | |
Params: | |
img: original image | |
corners: (N,2) detected images corners | |
pattern: Chessboard pattern layout | |
r: resolution per square in canonical image | |
""" | |
assert (pattern[0] % 2 == 0) != (pattern[1] % 2 == 0) | |
w = _warp(img, corners, pattern, r) | |
nf = _needs_flip(w, r) | |
if nf: | |
corners = corners[::-1] | |
return corners, nf | |
def main(): | |
import matplotlib.pyplot as plt | |
def _make_cb(shape, ts=1): | |
"""Generate a chessboard image""" | |
s = (shape[1] + 1, shape[0] + 1) | |
inner = np.kron(np.indices(s).sum(axis=0) % 2, np.ones((ts, ts))) | |
img = np.ones((inner.shape[0] + 2 * ts, inner.shape[1] + 2 * ts)) | |
img[ts:-ts, ts:-ts] = inner | |
return np.tile(np.expand_dims(img.copy(), -1), (1, 1, 3)).astype(np.uint8) * 255 | |
pattern = (13, 4) | |
img = _make_cb(pattern, 50) | |
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) | |
ret, corners = cv2.findChessboardCornersSB(img, pattern) | |
vis = cv2.drawChessboardCorners(img.copy(), pattern, corners, ret) | |
fig, ax = plt.subplots() | |
ax.imshow(vis[..., ::-1], interpolation="nearest") | |
img2 = _warp(img, corners, pattern, 2) | |
fig, ax = plt.subplots() | |
ax.imshow(img2, interpolation="nearest") | |
corners, flipped = ensure_corner_ordering(img, corners, pattern, r=2) | |
print(f"Flipped:{flipped=}") | |
vis = cv2.drawChessboardCorners(img.copy(), pattern, corners, ret) | |
fig, ax = plt.subplots() | |
ax.imshow(vis[..., ::-1]) | |
plt.show() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's a 9x6 pattern + corners as detected by cv2.findChessboardCornersSB
ensure_corner_order
changes this to