Merge branch 'remote' into dev

This commit is contained in:
ykcheung
2026-02-01 10:47:20 +08:00
5 changed files with 303 additions and 184 deletions
+6 -6
View File
@@ -36,7 +36,7 @@ def setup_simple_raft():
"""
Setup a simpler version using available models
"""
model = raft_large(pretrained=True, progress=False).to('cuda')
model = raft_small(pretrained=True, progress=False).to('cuda')
model = model.eval()
# Alternative: Use LiteFlowNet (lighter alternative)
print("RAFT loaded successfully")
@@ -61,7 +61,6 @@ def track_raft(prev_frame, frame, model, device):
# Transpose to (H, W, 2) format expected by draw_arrows
flow_np = flow_np.transpose(1, 2, 0) # (2, H, W) -> (H, W, 2)
# torch.onnx.export(
# model, # model to export
# (prev_tensor, curr_tensor), # inputs of the model,
@@ -96,10 +95,11 @@ def main_simple():
dy = 0
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame1 = frame[420:660, 840:1080, :]
frame2 = frame[420+dy:660+dy, 840+dx:1080+dx, :]
frame3 = frame[420-dy:660-dy, 840-dx:1080-dx, :]
# frame2 = frame[420+dy:660+dy, 840+dx:1080+dx, :]
# frame3 = frame[420-dy:660-dy, 840-dx:1080-dx, :]
frame = np.concatenate([frame3, frame1, frame2], axis=1)
# frame = np.concatenate([frame3, frame1, frame2], axis=1)
frame = frame1
# cv2.imshow("frame", frame3)
# cv2.waitKey(0)
if prev_gray is None:
@@ -137,7 +137,7 @@ def main_simple():
arrow_scale=1.0
)
arrows = cv2.resize(arrows, (1200, 400))
arrows = cv2.resize(arrows, (400, 400))
cv2.imshow('Optical Flow', arrows)
# prev_gray = gray.copy()
+113
View File
@@ -0,0 +1,113 @@
import os
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
import cv2
import orisys
import numpy as np
import time
def main():
# 步骤1:创建传感器对象 (必要步骤),
# config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径)
# cal_path为校准文件路径, 每一个设备需要创建/使用一个校准文件
# 方式1: 从视频文件读取
sensor = orisys.Sensor(0, config_name="./config/prototype_of3.json", cal_path= "./config/dd01a_0.npy", isstitch=True, cuda=True, verbose=True)
# 方式2: 从摄像头读取(取消注释使用)
# sensor = orisys.Sensor(0, config_name="./config/prototype_of3.json", cal_path= "./config/cal.npy", isstitch=True, cuda=True, verbose=True)
print("\n按 'q' 退出程序,按 'r' 重置光流追踪器")
while True:
# 步骤2:信息提取
# 步骤 2.1: 获取并拼接图像 (必要步骤)
start_time = time.perf_counter()
sensor.get_img()
end_time = time.perf_counter()
get_img_time = end_time - start_time
# if sensor.detect_motion(threshold=2):
# print("motion detected")
# 步骤 2.2: 计算形变场 (必要步骤)
start_time = time.perf_counter()
sensor.compute_deformation()
end_time = time.perf_counter()
compute_deformation_time = end_time - start_time
# 步骤 2.3: 计算触碰点
start_time = time.perf_counter()
sensor.compute_contact()
end_time = time.perf_counter()
compute_contact_time = end_time - start_time
#步骤 2.4: 读取信息
start_time = time.perf_counter()
fps, flow, vnormal, img, contour, centroid, depth_map = sensor.read_info(
sensor.info.FPS,
sensor.info.VRAW,
sensor.info.VNORMAL,
sensor.info.IMG,
sensor.info.CONTOUR,
sensor.info.CENTROID,
sensor.info.DEPTH
)
end_time = time.perf_counter()
read_info_time = end_time - start_time
#步骤 3: 可视化显示
# 步骤 3.1: 绘制形变场
start_time = time.perf_counter()
# arrows = orisys.util.draw_arrows(
# img,
# flow,
# threshold=4,
# grid_spacing=40,
# arrow_scale=1.0
# )
# cv2.imshow("Deformation", arrows)
# # 步骤 3.2: 在图像上触碰点和区域
# image_with_foe = orisys.util.draw_contact(img, contour, centroid)
# cv2.imshow("Contact Centroid", image_with_foe)
# # 步骤 3.3: 绘制深度图
# # 归一化散度值到0-255范围(使用绝对值,然后归一化)
# div_abs = depth_map
# if div_abs.max() > div_abs.min():
# div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8)
# else:
# div_normalized = np.zeros_like(div_abs, dtype=np.uint8)
# # 应用颜色映射(使用JET:蓝色=低散度,红色=高散度)
# cv2.imshow("Depth Map", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET))
#步骤 3.4: 打印资料
# print(f"fps: {fps}")
end_time = time.perf_counter()
display_time = end_time - start_time
# Get detailed timing from sensor (capture vs processing)
capture_time = sensor._last_capture_time
processing_time = sensor._last_processing_time
total_time = get_img_time + compute_deformation_time + compute_contact_time + read_info_time + display_time
print(f"get_img: {get_img_time*1000:.2f}ms (capture: {capture_time*1000:.2f}ms, processing: {processing_time*1000:.2f}ms), deformation: {compute_deformation_time*1000:.2f}ms, contact: {compute_contact_time*1000:.2f}ms, read: {read_info_time*1000:.2f}ms, display: {display_time*1000:.2f}ms, total: {total_time*1000:.2f}ms")
#步骤 3.5: 键盘控制
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
print("\n退出程序...")
break
if key == ord("r"):
# 重置光流追踪器(当追踪漂移时使用)
sensor.reset()
print("传感器已重置")
# =========================================================================
# 4. 清理资源
# =========================================================================
sensor.disconnect() # 释放视频源和相关资源
if __name__ == '__main__':
main()
+5 -3
View File
@@ -2,11 +2,11 @@
"name": "prototype",
"version": "1.0",
"camera": {
"resolution_raw": [960, 540],
"resolution_raw": [1920, 1080],
"fps": 30,
"units": 15,
"units_used": 9,
"resolution_stitch": [200, 200]
"resolution_stitch": [400, 400]
},
"stitcher": {
"min_area_frac": 0.01,
@@ -24,8 +24,10 @@
"lower_threshold": 12.0,
"upper_threshold": 18.0,
"loop_closure_interval": 1,
"algorithm": "dis",
"dis_preset": "medium",
"of_mode": 3
"dis_mode": 3,
"algorithm_list": ["raft", "dis"]
},
"contact": {
"min_contour_area": 3000,
+39 -22
View File
@@ -85,7 +85,6 @@ class Sensor:
self.config_name = config_name
self.cal_path = cal_path
self.mirror = mirror
# Load configuration
self.__config_cam = None
@@ -94,17 +93,19 @@ class Sensor:
self._load_config()
# State variables
self.stitch_valid = False
self.stitch_calibration = []
self.stitch_valid = False
self.compute_deformation_valid = False
self.compute_contact_valid = False
# Data Variables (public)
shape = (self.__config_cam["resolution_stitch"][1], self.__config_cam["resolution_stitch"][0])
# Image Data Variables (public)
self.stitch_calibration = []
self.frame = self.img = None
self.initial_img = None
shape = (self.__config_cam["resolution_stitch"][1], self.__config_cam["resolution_stitch"][0])
self.img_size_raw = None
self.img_filtered = None
self.initial_img = None
# Optical Flow Data Variables (public)
self.flow = np.zeros(shape + (2,), dtype=np.float32)
self.vnormal = np.zeros_like(self.flow)
self.vshear = np.zeros_like(self.flow)
@@ -120,16 +121,18 @@ class Sensor:
# Timing
self.last_log_time = time.perf_counter()
self._fps = 0
self._last_capture_time = 0.0
self._last_processing_time = 0.0
# Async pipeline state
# self._async_enabled = False
self._capture_thread = None
self._flow_thread = None
self._compute_thread = None
self._capture_queue = None
self._flow_queue = None
self._result_queue = None
self._stop_event = None
# self._capture_thread = None
# self._flow_thread = None
# self._compute_thread = None
# self._capture_queue = None
# self._flow_queue = None
# self._result_queue = None
# self._stop_event = None
@@ -147,7 +150,7 @@ class Sensor:
print(f" Stitch mode: {self.stitch_valid}")
print(f" CUDA acceleration: {self.cuda}")
print(f" CuPy available: {self.CUPY_AVAILABLE}")
print(f" OpenCV CUDA devices: {cv2.cuda.getCudaEnabledDeviceCount()}")
print(f" Optical flow algorithm: {self.__config_tracker['algorithm']}")
# =========================================================================
# Public API - Core Methods (Most Frequently Used)
@@ -160,17 +163,25 @@ class Sensor:
Returns:
np.ndarray: Processed grayscale image
"""
# Simple direct read - buffer size of 1 should minimize frame accumulation
# Timing measurement helps identify if capture or processing is the bottleneck
t_capture_start = time.perf_counter()
ret, frame = self.cap.read()
t_capture_end = time.perf_counter()
self._last_capture_time = t_capture_end - t_capture_start
if not ret:
print("Failed to get_img from video source.")
return None
# Start processing timing
t_processing_start = time.perf_counter()
self.frame = frame.copy()
# Convert to grayscale
# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = frame
# Extract and stitch tiles
self.stitcher.get_stitch_unit(gray)
self._extract_tiles(gray)
self.stitcher.get_stitch_unit(frame)
self._extract_tiles(frame)
img = np.ascontiguousarray(self.stitcher.stitch_rt_optimized())
@@ -182,7 +193,9 @@ class Sensor:
img = cv2.flip(img, 1)
self.img = img.astype(np.uint8)
t_processing_end = time.perf_counter()
self._last_processing_time = t_processing_end - t_processing_start
# FPS calculation
self._fps = 1.0 / (time.perf_counter() - self.last_log_time)
@@ -415,9 +428,13 @@ class Sensor:
self.cap = cv2.VideoCapture(self.vid_src)
# Set buffer size to 1 to reduce latency and avoid frame accumulation
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
# Note: Not all backends support this, but it helps when available
try:
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
except:
pass # Some backends don't support this property
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M', 'J', 'P', 'G'))
self.cap.set(cv2.CAP_PROP_FPS, self._config_fps)
# self.cap.set(cv2.CAP_PROP_FPS, self._config_fps)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._config_resolution_raw[0])
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._config_resolution_raw[1])
+140 -153
View File
@@ -26,7 +26,23 @@ class Tracker:
self.__frame_count = 0
dis_preset = config_tracker["dis_preset"]
loop_closure_interval = config_tracker["loop_closure_interval"]
if not self.cuda:
self.__algorithm = config_tracker["algorithm"]
if self.__algorithm == "raft" and (not self.cuda or not torch.cuda.is_available()):
self.__algorithm = "dis"
if self.verbose:
print("No CUDA available, falling back to OPENCV DIS optical flow")
if self.__algorithm == "raft":
### RAFT
self.__raft_device = "cuda"
print(f"RAFT device: {self.__raft_device}")
self.__raft_model = self.setup_simple_raft()
self.__raft_ref_frame = None
self.__raft_ref_tensor = None
elif self.__algorithm == "dis":
# DIS method - optimize for speed
if dis_preset == 'ultrafast':
preset = cv2.DISOPTICAL_FLOW_PRESET_ULTRAFAST
@@ -35,98 +51,35 @@ class Tracker:
else:
preset = cv2.DISOPTICAL_FLOW_PRESET_MEDIUM
self.__tracking_inst = cv2.DISOpticalFlow_create(preset)
self.__tracking_inst = cv2.DISOpticalFlow_create(preset)
# Aggressive optimization for CPU
self.__tracking_inst.setFinestScale(config_tracker["of_mode"]) # Skip finest pyramid level (2x faster)
self.__tracking_inst.setFinestScale(config_tracker["dis_mode"]) # Skip finest pyramid level (2x faster)
# else:
# # CUDA flow method with stream support
# self.__tracking_inst = cv2.cuda.FarnebackOpticalFlow_create()
# # Create CUDA stream for async operations
# self.__cuda_stream = cv2.cuda.Stream()
# else:
# # CUDA flow method with stream support
# self.__tracking_inst = cv2.cuda.FarnebackOpticalFlow_create()
# # Create CUDA stream for async operations
# self.__cuda_stream = cv2.cuda.Stream()
self.__fine_base_frame_queue = []
self.__fine_flow_queue = []
self.__prev_fine_flow = None
self.__adaptive = adaptive
self.__lower_threshold = config_tracker["lower_threshold"] # error lower than threshold will close the loop
self.__upper_threshold = config_tracker["upper_threshold"] # error greater than threshold will add new reference frame
### RAFT
self.__raft_device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"RAFT device: {self.__raft_device}")
self.__raft_model = self.setup_simple_raft()
self.__raft_ref_frame = None
self.__raft_ref_tensor = None
self.__fine_base_frame_queue = []
self.__fine_flow_queue = []
self.__prev_fine_flow = None
self.__adaptive = adaptive
self.__lower_threshold = config_tracker["lower_threshold"] # error lower than threshold will close the loop
self.__upper_threshold = config_tracker["upper_threshold"] # error greater than threshold will add new reference frame
def track(self, frame):
"""Main tracking function that dispatches to CUDA or CPU implementation"""
if self.cuda:
# return self.track_cuda(frame)
if self.__algorithm == "raft":
return self.track_raft(frame)
else:
elif self.__algorithm == "dis":
if frame.ndim == 3:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return self.track_cpu(frame)
def track_cuda(self, frame):
"""CUDA-accelerated optical flow tracking"""
frame_height, frame_width = frame.shape[:2]
frame = cv2.cuda_GpuMat(frame)
if self.__adaptive:
if not self.__fine_base_frame_queue:
self.__fine_base_frame_queue.append(frame)
# Pre-compute meshgrid for remap operations
x = np.linspace(0, frame_width - 1, frame_width, dtype='float32')
y = np.linspace(0, frame_height - 1, frame_height, dtype='float32')
self.map = np.array(np.meshgrid(x, y)).transpose((1, 2, 0))
cuMat = cv2.cuda_GpuMat()
cuMat.upload(self.map)
self.map = cuMat
self.__fine_flow_queue.append(
cv2.cuda_GpuMat(frame.size(), cv2.CV_32FC2, (0.0, 0.0)))
self.__prev_fine_flow = cv2.cuda_GpuMat(
frame.size(), cv2.CV_32FC2, (0.0, 0.0))
return self.__fine_flow_queue[-1]
else:
self.__frame_count += 1
# Only check loop closure periodically (expensive operation)
if self.__frame_count % self.__loop_closure_interval == 0:
self.__checkLoopClosure(frame)
# Use CUDA stream for async optical flow computation
self.__prev_fine_flow = self.__tracking_inst.calc(
self.__fine_base_frame_queue[-1], frame,
self.__prev_fine_flow, self.__cuda_stream)
final_flow = cv2.cuda.add(self.__fine_flow_queue[-1],
self.__prev_fine_flow,
stream=self.__cuda_stream)
# Use fine flow for error estimation
error = self.__getForwardBackwardError(
self.__fine_base_frame_queue[-1], frame,
self.__prev_fine_flow)
if error > self.__upper_threshold:
self.__fine_base_frame_queue.append(frame)
self.__fine_flow_queue.append(final_flow)
self.__prev_fine_flow = None
return final_flow
else:
if not self.__fine_base_frame_queue:
self.__fine_base_frame_queue.append(frame)
self.__prev_fine_flow = self.__tracking_inst.calc(
self.__fine_base_frame_queue[-1], frame,
self.__prev_fine_flow, self.__cuda_stream)
return self.__prev_fine_flow
def track_cpu(self, frame):
"""CPU-based optical flow tracking"""
frame_height, frame_width = frame.shape[:2]
@@ -185,58 +138,57 @@ class Tracker:
# return output
def __getForwardBackwardError(self, I0, I1, flow):
if self.cuda:
flow_map = cv2.cuda.add(self.map, flow)
map_x, map_y = cv2.cuda.split(flow_map)
z = cv2.cuda.remap(I1,
map_x,
map_y,
interpolation=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
t = I0.clone()
t = t.convertTo(rtype=cv2.CV_32FC1, dst=t)
z = z.convertTo(rtype=cv2.CV_32FC1, dst=z)
rev = cv2.cuda.absSum(cv2.cuda.subtract(
t, z))[0] / t.size()[0] / t.size()[1]
# if self.cuda:
# flow_map = cv2.cuda.add(self.map, flow)
# map_x, map_y = cv2.cuda.split(flow_map)
# z = cv2.cuda.remap(I1,
# map_x,
# map_y,
# interpolation=cv2.INTER_CUBIC,
# borderMode=cv2.BORDER_REPLICATE)
# t = I0.clone()
# t = t.convertTo(rtype=cv2.CV_32FC1, dst=t)
# z = z.convertTo(rtype=cv2.CV_32FC1, dst=z)
# rev = cv2.cuda.absSum(cv2.cuda.subtract(
# t, z))[0] / t.size()[0] / t.size()[1]
# else:
# Optimized CPU path - avoid redundant type conversions
flow_map = self.map + flow
z = cv2.remap(I1,
flow_map[:, :, 0],
flow_map[:, :, 1],
interpolation=cv2.INTER_CUBIC, # INTER_LINEAR faster than INTER_CUBIC
borderMode=cv2.BORDER_REPLICATE)
# Use in-place operations when possible
if I0.dtype != np.float32:
diff = z.astype('float32') - I0.astype('float32')
else:
# Optimized CPU path - avoid redundant type conversions
flow_map = self.map + flow
z = cv2.remap(I1,
flow_map[:, :, 0],
flow_map[:, :, 1],
interpolation=cv2.INTER_CUBIC, # INTER_LINEAR faster than INTER_CUBIC
borderMode=cv2.BORDER_REPLICATE)
# Use in-place operations when possible
if I0.dtype != np.float32:
diff = z.astype('float32') - I0.astype('float32')
else:
diff = z - I0
rev = np.mean(np.abs(diff))
diff = z - I0
rev = np.mean(np.abs(diff))
return rev
def __checkLoopClosure(
self, frame
): # update (prune) the frame queue with loop closure detection
def __checkLoopClosure(self, frame): # update (prune) the frame queue with loop closure detection
for i in range(len(self.__fine_base_frame_queue) - 2, -1, -1):
f = self.__tracking_inst.calc(
self.__fine_base_frame_queue[i], frame, None)
error = self.__getForwardBackwardError(
self.__fine_base_frame_queue[i], frame, f)
if error < self.__lower_threshold:
self.__fine_base_frame_queue = self.__fine_base_frame_queue[:
i +
1]
self.__fine_base_frame_queue = self.__fine_base_frame_queue[:i+1]
self.__fine_flow_queue = self.__fine_flow_queue[:i + 1]
return True
return False
def reset(self):
self.__fine_base_frame_queue = []
self.__fine_flow_queue = []
self.__prev_fine_flow = None
self.__frame_count = 0
self.__raft_ref_frame = None
self.__raft_ref_tensor = None
if self.__algorithm == "raft":
self.__raft_ref_frame = None
self.__raft_ref_tensor = None
elif self.__algorithm == "dis":
self.__fine_base_frame_queue = []
self.__fine_flow_queue = []
self.__prev_fine_flow = None
self.__frame_count = 0
def getFrameQueueLength(self):
return len(self.__fine_base_frame_queue)
@@ -251,22 +203,11 @@ class Tracker:
model = raft_large(pretrained=True, progress=False).to(self.__raft_device)
model = model.eval()
# Optimize matmul precision for faster inference (TF32 on Ampere+ GPUs)
# torch.set_float32_matmul_precision('medium')
# Enable CUDA-specific optimizations
if self.__raft_device == 'cuda':
# cuDNN optimizations
torch.backends.cudnn.benchmark = True # Auto-select fastest algorithms
torch.backends.cudnn.deterministic = False # Allow non-deterministic for speed
# TensorFloat-32 for Ampere+ GPUs (faster with minimal accuracy loss)
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
print(f"RAFT model loaded on {self.__raft_device}")
if self.verbose:
print(f"RAFT model loaded on {self.__raft_device}")
return model
def _preprocess_frame_to_tensor(self, frame, resize=None):
def _preprocess_frame_to_tensor(self, frame):
"""Efficiently convert numpy frame to RAFT-ready tensor"""
# Ensure contiguous memory for faster tensor conversion
if not frame.flags['C_CONTIGUOUS']:
@@ -275,7 +216,6 @@ class Tracker:
# Upload to device as uint8 first to save bandwidth (4x less data than float32)
tensor = torch.from_numpy(frame).permute(2, 0, 1)
# Move to device with non_blocking for async transfer (if CUDA)
# Pin memory for faster CPU-GPU transfer (similar to DataLoader pin_memory=True)
if self.__raft_device == 'cuda':
# Pin memory before transferring to GPU (must be on CPU first)
@@ -287,10 +227,6 @@ class Tracker:
# Convert to float and normalize on device
tensor = tensor.float() / 255.0
# Resize if needed
if resize is not None:
tensor = T.Resize(resize)(tensor)
# Normalize to [-1, 1]
tensor = (tensor - 0.5) * 2.0
@@ -300,30 +236,25 @@ class Tracker:
return tensor
def track_raft(self, frame):
def track_raft(self, frame, gpu = False):
# No need to copy if we just read
# frame is expected to be (H, W, 3) or (H, W)
if frame.ndim == 2:
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB)
elif frame.shape[2] == 4:
frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)
elif frame.shape[2] == 3:
# Assume BGR input from OpenCV
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
if self.__raft_ref_frame is None:
self.__raft_ref_frame = frame # Keep reference for potential debug/CPU use
# Cache the processed tensor to avoid re-processing reference frame every time
self.__raft_ref_frame = frame
self.__raft_ref_tensor = self._preprocess_frame_to_tensor(frame)
prev_tensor = self.__raft_ref_tensor
curr_tensor = self._preprocess_frame_to_tensor(frame)
# Synchronize before timing to ensure preprocessing is complete
if self.__raft_device == 'cuda':
torch.cuda.synchronize()
if self.verbose:
t0 = time.perf_counter()
with torch.inference_mode():
if self.__raft_device == 'cuda':
with torch.amp.autocast('cuda'):
@@ -331,14 +262,70 @@ class Tracker:
else:
flow = self.__raft_model(prev_tensor, curr_tensor)
predicted_flows = flow[-1]
# Synchronize after inference to get accurate timing
if self.__raft_device == 'cuda':
torch.cuda.synchronize()
if self.verbose:
t1 = time.perf_counter()
print(f"RAFT time: {t1 - t0}")
# Optimize tensor->numpy conversion: contiguous() ensures optimal memory layout
flow_np = predicted_flows[0].contiguous().cpu().numpy()
# Transpose to (H, W, 2) format expected by draw_arrows
flow_np = flow_np.transpose(1, 2, 0) # (2, H, W) -> (H, W, 2)
return flow_np
return flow_np
# def track_cuda(self, frame):
# """CUDA-accelerated optical flow tracking"""
# frame_height, frame_width = frame.shape[:2]
# frame = cv2.cuda_GpuMat(frame)
# if self.__adaptive:
# if not self.__fine_base_frame_queue:
# self.__fine_base_frame_queue.append(frame)
# # Pre-compute meshgrid for remap operations
# x = np.linspace(0, frame_width - 1, frame_width, dtype='float32')
# y = np.linspace(0, frame_height - 1, frame_height, dtype='float32')
# self.map = np.array(np.meshgrid(x, y)).transpose((1, 2, 0))
# cuMat = cv2.cuda_GpuMat()
# cuMat.upload(self.map)
# self.map = cuMat
# self.__fine_flow_queue.append(
# cv2.cuda_GpuMat(frame.size(), cv2.CV_32FC2, (0.0, 0.0)))
# self.__prev_fine_flow = cv2.cuda_GpuMat(
# frame.size(), cv2.CV_32FC2, (0.0, 0.0))
# return self.__fine_flow_queue[-1]
# else:
# self.__frame_count += 1
# # Only check loop closure periodically (expensive operation)
# if self.__frame_count % self.__loop_closure_interval == 0:
# self.__checkLoopClosure(frame)
# # Use CUDA stream for async optical flow computation
# self.__prev_fine_flow = self.__tracking_inst.calc(
# self.__fine_base_frame_queue[-1], frame,
# self.__prev_fine_flow, self.__cuda_stream)
# final_flow = cv2.cuda.add(self.__fine_flow_queue[-1],
# self.__prev_fine_flow,
# stream=self.__cuda_stream)
# # Use fine flow for error estimation
# error = self.__getForwardBackwardError(
# self.__fine_base_frame_queue[-1], frame,
# self.__prev_fine_flow)
# if error > self.__upper_threshold:
# self.__fine_base_frame_queue.append(frame)
# self.__fine_flow_queue.append(final_flow)
# self.__prev_fine_flow = None
# return final_flow
# else:
# if not self.__fine_base_frame_queue:
# self.__fine_base_frame_queue.append(frame)
# self.__prev_fine_flow = self.__tracking_inst.calc(
# self.__fine_base_frame_queue[-1], frame,
# self.__prev_fine_flow, self.__cuda_stream)
# return self.__prev_fine_flow