Skip to content

Instantly share code, notes, and snippets.

@cheind
Last active September 17, 2024 08:06
Show Gist options
  • Save cheind/423642ec9995a8ecfd6e716642010a1c to your computer and use it in GitHub Desktop.
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.
"""
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()
@cheind
Copy link
Author

cheind commented Sep 17, 2024

Here's a 9x6 pattern + corners as detected by cv2.findChessboardCornersSB

a

ensure_corner_order changes this to

b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment