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

357 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
import cv2
# Try to import CuPy for GPU acceleration
try:
import cupy as cp
CUPY_AVAILABLE = True
except ImportError:
cp = None
CUPY_AVAILABLE = False
# 缓存形态学操作的核,避免重复创建
_morphology_kernels = {}
class Contact3D:
def __init__(self, config_contact=None, verbose=False):
# Load Config
self.config_contact = config_contact
self.flow_threshold = config_contact["flow_threshold"]
self.expansion_threshold = config_contact["expansion_threshold"]
self.downsample_factor = 1
self.kernel_size = config_contact["kernel_size"]
self.denoise_sigma = config_contact["denoise_sigma"]
self.min_contour_area = config_contact["min_contour_area"]
# Divergence threshold: only keep divergence values above this threshold, set others to zero
self.divergence_threshold = config_contact["div_threshold"]
self.verbose = verbose
if self.verbose:
print(f"Contact3D initialized with config: {self.config_contact}")
# Data Variables
self.foe_results = None
# self.expansion_mask = None
# self.contraction_mask = None
# self.divergence_map = None
# self.centroid = None
# self.centroid_found = False
# self.contour = None
# self.all_contours = None
def _create_foe_result(self, expansion_mask, contraction_mask, divergence_map,
centroid=None, centroid_found=False, contour=None,
all_contours=None):
"""
创建标准化的FOE结果字典,统一所有函数的返回格式。
Args:
expansion_mask: 扩张区域掩码
contraction_mask: 收缩区域掩码
divergence_map: 散度图
centroid: 质心坐标,可以是单个元组 (x, y) 或列表 [(x1, y1), (x2, y2), ...]
centroid_found: 是否找到质心
contour: 轮廓,可以是单个轮廓或轮廓列表
all_contours: 所有轮廓(仅在return_all_contours=True时使用)
Returns:
dict: 标准化的FOE结果字典
"""
result = {
'expansion_mask': expansion_mask,
'contraction_mask': contraction_mask,
'divergence_map': divergence_map,
'centroid': centroid if centroid is not None else [0,0],
'centroid_found': centroid_found,
}
# 可选字段
if contour is not None:
result['contour'] = contour
if all_contours is not None:
result['_all_contours'] = all_contours
return result
def compute_foe(self, flow_field, return_all_contours=False):
"""
计算光流场的扩张焦点(Focus of Expansion)。
Args:
flow_field: 光流场,形状为 (H, W, 2) 的 numpy 数组
min_magnitude: 最小光流幅度阈值,低于此值的点不参与计算
expansion_threshold: 扩张判定阈值,用于区分扩张和收缩区域
downsample_factor: 下采样因子,用于平滑计算(默认10)
return_all_contours: 是否返回所有轮廓(默认False,仅返回最大轮廓)
kernel_size: 形态学操作的核大小
denoise_sigma: 降噪高斯滤波的标准差(默认1.5,设为0禁用降噪)
Returns:
dict: 包含扩张焦点信息的字典
"""
H, W = flow_field.shape[:2]
# 优化:使用视图而不是复制,减少内存分配
u = flow_field[:, :, 0]
v = flow_field[:, :, 1]
# 下采样并计算散度,然后插值回原尺寸
if self.downsample_factor > 1:
# 使用步进切片,避免复制
u_ds = u[::self.downsample_factor, ::self.downsample_factor]
v_ds = v[::self.downsample_factor, ::self.downsample_factor]
# 计算幅度(优化:使用square减少中间数组)
magnitude_ds = np.sqrt(np.square(u_ds) + np.square(v_ds))
if self.denoise_sigma > 0:
magnitude_ds = cv2.GaussianBlur(magnitude_ds, (0, 0), self.denoise_sigma)
# 优化:合并梯度计算,减少中间数组
# 计算散度:du/dx + dv/dy
du_dx = np.gradient(u_ds, axis=1)
dv_dy = np.gradient(v_ds, axis=0)
divergence_ds = du_dx + dv_dy # 直接相加,避免中间数组
if self.denoise_sigma > 0:
divergence_ds = cv2.GaussianBlur(divergence_ds, (0, 0), self.denoise_sigma)
# 插值回原尺寸
magnitude = cv2.resize(magnitude_ds, (W, H), interpolation=cv2.INTER_CUBIC)
divergence = cv2.resize(divergence_ds, (W, H), interpolation=cv2.INTER_CUBIC)
else:
# 计算幅度(优化:使用square减少中间数组)
magnitude = np.sqrt(np.square(u) + np.square(v))
if self.denoise_sigma > 0:
magnitude = cv2.GaussianBlur(magnitude, (0, 0), self.denoise_sigma)
# 优化:合并梯度计算
du_dx = np.gradient(u, axis=1)
dv_dy = np.gradient(v, axis=0)
divergence = du_dx + dv_dy
# 降噪:在计算散度之后对散度图进行平滑
if self.denoise_sigma > 0:
divergence = cv2.GaussianBlur(divergence, (0, 0), self.denoise_sigma)
# 优化:使用原地操作 - 只保留大于阈值的散度值,其他设为0
divergence = np.where(divergence > self.divergence_threshold, divergence, 0)
# 创建掩码(使用原地操作优化)
valid_mask = magnitude > self.flow_threshold
expansion_mask = (valid_mask & (divergence > self.expansion_threshold))
contraction_mask = (valid_mask & (divergence < -self.expansion_threshold))
# 初始化结果字典
result = self._create_foe_result(expansion_mask, contraction_mask, divergence)
# 寻找扩张区域轮廓
# 优化:缓存形态学操作的核
# Convert kernel_size to tuple for use as dictionary key (lists are not hashable)
kernel_key = tuple(self.kernel_size) if isinstance(self.kernel_size, (list, np.ndarray)) else self.kernel_size
if kernel_key not in _morphology_kernels:
_morphology_kernels[kernel_key] = np.ones(self.kernel_size, np.uint8)
expansion_binary = expansion_mask.astype(np.uint8) * 255
if np.any(expansion_binary):
expansion_binary_closed = cv2.morphologyEx(expansion_binary, cv2.MORPH_CLOSE, _morphology_kernels[kernel_key])
contours, _ = cv2.findContours(expansion_binary_closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
result['centroid_found'] = True
if return_all_contours:
result['_all_contours'] = contours
largest_contour = max(contours, key=cv2.contourArea)
result['contour'] = largest_contour
M = cv2.moments(largest_contour)
if M["m00"] > 0:
result['centroid'] = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
self.foe_results = result
return self.foe_results
def compute_foe_gpu(self, flow_field, return_all_contours=False):
"""
使用CUDA加速计算光流场的扩张焦点(Focus of Expansion)。
Args:
flow_field: 光流场,形状为 (H, W, 2) 的 numpy 数组
flow_threshold: 最小光流幅度阈值,低于此值的点不参与计算
expansion_threshold: 扩张判定阈值,用于区分扩张和收缩区域
downsample_factor: 下采样因子,用于平滑计算(默认10)
return_all_contours: 是否返回所有轮廓(默认False,仅返回最大轮廓)
kernel_size: 形态学操作的核大小
denoise_sigma: 降噪高斯滤波的标准差(默认1.5,设为0禁用降噪)
Returns:
dict: 包含扩张焦点信息的字典
Note:
如果CuPy不可用,会自动回退到CPU版本的compute_foe
"""
# 检查CuPy是否可用
if not CUPY_AVAILABLE:
return self.compute_foe(flow_field, return_all_contours)
H, W = flow_field.shape[:2]
# 导入CuPy的高斯滤波函数(仅在需要时导入)
try:
from cupyx.scipy.ndimage import gaussian_filter as cp_gaussian_filter
except ImportError:
# 如果导入失败,回退到CPU版本
return self.compute_foe(flow_field, return_all_contours)
# 优化:使用asarray而不是array,如果已经是CuPy数组则避免复制
if isinstance(flow_field, cp.ndarray):
flow_gpu = flow_field
else:
flow_gpu = cp.asarray(flow_field, dtype=cp.float32)
# 优化:使用视图而不是复制
u_gpu = flow_gpu[:, :, 0]
v_gpu = flow_gpu[:, :, 1]
# 下采样并计算散度,然后插值回原尺寸
if self.downsample_factor > 1:
# 下采样(使用步进切片,避免复制)
u_ds_gpu = u_gpu[::self.downsample_factor, ::self.downsample_factor]
v_ds_gpu = v_gpu[::self.downsample_factor, ::self.downsample_factor]
# 计算幅度(优化:使用square减少中间数组)
magnitude_ds_gpu = cp.sqrt(cp.square(u_ds_gpu) + cp.square(v_ds_gpu))
# 高斯滤波(使用CuPy的gaussian_filter)
if self.denoise_sigma > 0:
magnitude_ds_gpu = cp_gaussian_filter(magnitude_ds_gpu, sigma=self.denoise_sigma)
# 优化:合并梯度计算,减少中间数组
# 计算散度:du/dx + dv/dy
du_dx_ds = cp.gradient(u_ds_gpu, axis=1)
dv_dy_ds = cp.gradient(v_ds_gpu, axis=0)
divergence_ds_gpu = du_dx_ds + dv_dy_ds
# 对散度进行高斯滤波
if self.denoise_sigma > 0:
divergence_ds_gpu = cp_gaussian_filter(divergence_ds_gpu, sigma=self.denoise_sigma)
# 优化:在GPU上完成插值,减少数据传输
# 使用CuPy的zoom进行插值(更高效)
from cupyx.scipy.ndimage import zoom as cp_zoom
zoom_factor = (H / divergence_ds_gpu.shape[0], W / divergence_ds_gpu.shape[1])
magnitude_gpu = cp_zoom(magnitude_ds_gpu, zoom_factor, order=3) # 3 = cubic
divergence_gpu = cp_zoom(divergence_ds_gpu, zoom_factor, order=3)
else:
# 计算幅度(优化:使用square减少中间数组)
magnitude_gpu = cp.sqrt(cp.square(u_gpu) + cp.square(v_gpu))
# 高斯滤波
if self.denoise_sigma > 0:
magnitude_gpu = cp_gaussian_filter(magnitude_gpu, sigma=self.denoise_sigma)
# 优化:合并梯度计算
du_dx = cp.gradient(u_gpu, axis=1)
dv_dy = cp.gradient(v_gpu, axis=0)
divergence_gpu = du_dx + dv_dy
# 对散度进行高斯滤波
if self.denoise_sigma > 0:
divergence_gpu = cp_gaussian_filter(divergence_gpu, sigma=self.denoise_sigma)
# 优化:在GPU上完成掩码操作,减少数据传输
# 只保留大于阈值的散度值,其他设为0
divergence_gpu = cp.where(divergence_gpu > self.divergence_threshold, divergence_gpu, 0)
# 在GPU上创建掩码
valid_mask_gpu = magnitude_gpu > self.flow_threshold
expansion_mask_gpu = (valid_mask_gpu & (divergence_gpu > self.expansion_threshold))
contraction_mask_gpu = (valid_mask_gpu & (divergence_gpu < -self.expansion_threshold))
# 优化:一次性传输所有需要的数据回CPU
magnitude = cp.asnumpy(magnitude_gpu)
divergence = cp.asnumpy(divergence_gpu)
expansion_mask = cp.asnumpy(expansion_mask_gpu)
contraction_mask = cp.asnumpy(contraction_mask_gpu)
# 初始化结果字典
result = self._create_foe_result(expansion_mask, contraction_mask, divergence)
# 寻找扩张区域轮廓(在CPU上进行,因为OpenCV的轮廓检测需要CPU)
# 优化:缓存形态学操作的核
# Convert kernel_size to tuple for use as dictionary key (lists are not hashable)
kernel_key = tuple(self.kernel_size) if isinstance(self.kernel_size, (list, np.ndarray)) else self.kernel_size
if kernel_key not in _morphology_kernels:
_morphology_kernels[kernel_key] = np.ones(self.kernel_size, np.uint8)
expansion_binary = expansion_mask.astype(np.uint8) * 255
if np.any(expansion_binary):
expansion_binary_closed = cv2.morphologyEx(expansion_binary, cv2.MORPH_CLOSE, _morphology_kernels[kernel_key])
contours, _ = cv2.findContours(expansion_binary_closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
result['centroid_found'] = True
if return_all_contours:
result['_all_contours'] = contours
largest_contour = max(contours, key=cv2.contourArea)
result['contour'] = largest_contour
M = cv2.moments(largest_contour)
if M["m00"] > 0:
result['centroid'] = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
self.foe_results = result
return self.foe_results
def compute_multiple_foe(self,flow_field):
"""
计算光流场的多个扩张焦点(Foci of Expansion)。
Args:
flow_field: 光流场,形状为 (H, W, 2) 的 numpy 数组
flow_threshold: 最小光流幅度阈值,低于此值的点不参与计算
expansion_threshold: 扩张判定阈值,用于区分扩张和收缩区域
downsample_factor: 下采样因子,用于平滑计算(默认10)
min_contour_area: 最小轮廓面积阈值,小于此值的轮廓会被忽略
denoise_sigma: 降噪高斯滤波的标准差(默认1.5,设为0禁用降噪)
kernel_size: 形态学操作的核大小
Returns:
dict: 包含多个扩张焦点信息的字典,包括:
- 'foe_points': 扩张焦点坐标列表 [(x1, y1), (x2, y2), ...]
- 'expansion_regions': 扩张区域轮廓列表
- 'expansion_mask': 扩张区域二值掩码
- 'contraction_mask': 收缩区域二值掩码
- 'divergence_map': 散度图
"""
# 调用 compute_foe_gpu 获取基础数据和所有轮廓
foe_result = self.compute_foe_gpu(flow_field, return_all_contours=True)
# 构建多焦点结果,复用基础数据
result = self._create_foe_result(
expansion_mask=foe_result['expansion_mask'],
contraction_mask=foe_result['contraction_mask'],
divergence_map=foe_result['divergence_map'],
centroid_found=foe_result['centroid_found']
)
# 处理所有轮廓,过滤并计算焦点
if '_all_contours' in foe_result:
for contour in foe_result['_all_contours']:
# 过滤掉太小的轮廓
if cv2.contourArea(contour) >= self.min_contour_area:
# 计算轮廓的质心作为焦点
M = cv2.moments(contour)
if M["m00"] > 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
result['centroid'].append((cx, cy))
if 'contour' not in result:
result['contour'] = []
result['contour'].append(contour)
self.foe_results = result
return self.foe_results