Files
orisys-sdk/src/orisys/util.py
T

129 lines
4.1 KiB
Python

import cv2
import numpy as np
def _prepare_draw_mask(valid_mask, shape):
"""Resize a boolean mask to (height, width) for arrow culling."""
if valid_mask is None:
return None
h, w = shape[:2]
mask = np.asarray(valid_mask, dtype=bool)
if mask.shape != (h, w):
mask_u8 = mask.astype(np.uint8)
if mask_u8.ndim > 2:
mask_u8 = mask_u8[:, :, 0]
mask = cv2.resize(mask_u8, (w, h), interpolation=cv2.INTER_NEAREST).astype(bool)
return mask
def draw_arrows(
frame,
optical_flow,
fps=True,
threshold=5,
grid_spacing=40,
arrow_scale=1.0,
margin=0.8,
colormap=cv2.COLORMAP_JET,
below_threshold_color=(0, 0, 255),
edge_margin=15,
valid_mask=None,
):
"""
Draw colored optical flow arrows on the image by magnitude (JET colormap).
Frame and optical_flow must have the same height/width; arrows are drawn in pixel coordinates.
Parameters
----------
valid_mask : array-like, optional
Boolean (H, W) mask. Grid points outside the mask are skipped (no arrows or dots).
"""
image = frame.copy()
if len(image.shape) == 2 or image.shape[2] == 1:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
h, w = image.shape[:2]
mask = _prepare_draw_mask(valid_mask, (h, w))
# Arrow grid in pixel coordinates (assume frame and flow same size)
rr = np.arange(grid_spacing // 2, h, grid_spacing)
cc = np.arange(grid_spacing // 2, w, grid_spacing)
rr, cc = np.meshgrid(rr, cc, indexing="ij")
rr, cc = rr.reshape(-1), cc.reshape(-1)
# Displacement at each grid point (flow stored as half-displacement)
disp = 2.0 * optical_flow[rr, cc, :]
mag = np.linalg.norm(disp, axis=1)
dx = disp[:, 0] / arrow_scale
dy = disp[:, 1] / arrow_scale
length = np.sqrt(dx * dx + dy * dy)
# Cap arrow length at grid_spacing
scale = np.where(length > grid_spacing * margin, grid_spacing / np.maximum(length, 1e-9), 1.0) * margin
dx, dy = dx * scale, dy * scale
end_x = (cc + dx).astype(np.int32)
end_y = (rr + dy).astype(np.int32)
valid = (
(cc >= edge_margin)
& (cc < w - edge_margin)
& (rr >= edge_margin)
& (rr < h - edge_margin)
& (end_x >= 0)
& (end_x < w)
& (end_y >= 0)
& (end_y < h)
)
if mask is not None:
valid &= mask[rr, cc]
# Arrow tips may fall outside the frame (the length cap is applied
# before the bounds check above), so clamp before indexing: those points
# are already False in ``valid``, and in-bounds indices are unchanged.
valid &= mask[np.clip(end_y, 0, h - 1), np.clip(end_x, 0, w - 1)]
mag_range = mag.max() - mag.min()
norm_mag = (
((mag - mag.min()) / mag_range * 255).astype(np.uint8) if mag_range > 0 else np.zeros_like(mag, dtype=np.uint8)
)
norm_mag[mag < threshold] = 0
colors = cv2.applyColorMap(norm_mag.reshape(-1, 1), colormap)
for i in np.where(valid)[0]:
pt1, pt2 = (int(cc[i]), int(rr[i])), (int(end_x[i]), int(end_y[i]))
color = tuple(int(c) for c in colors[i, 0])
if mag[i] < threshold:
cv2.circle(image, pt1, radius=1, color=below_threshold_color, thickness=-1)
else:
thickness = 1
cv2.arrowedLine(image, pt1, pt2, color, thickness=thickness, tipLength=0.2)
return image
def draw_contact(image, contour, centroid, point_color=(0, 255, 0), region_color=(0, 255, 255)):
"""
在图像上绘制扩张焦点和扩张区域。
Args:
image: 背景图像
contour: 轮廓
centroid: 触碰点
point_color: 触碰点的颜色 (BGR)
region_color: 轮廓的颜色 (BGR)
Returns:
np.ndarray: 绘制了触碰点和轮廓的图像
"""
image = image.copy()
# 绘制扩张区域轮廓
if contour is not None:
cv2.drawContours(image, [contour], -1, region_color, 2)
# 绘制触碰点
if centroid is not None:
cx, cy = centroid
cv2.circle(image, (cx, cy), 8, point_color, -1)
cv2.circle(image, (cx, cy), 10, point_color, 2)
return image