diff --git a/example1_basic_pipeline.py b/example1_basic_pipeline.py index 3d7bb1a..0c444dd 100644 --- a/example1_basic_pipeline.py +++ b/example1_basic_pipeline.py @@ -14,14 +14,30 @@ os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" import cv2 import orisys import numpy as np +import argparse def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--video","-v", type=str, default="0", help="Camera ID / Video file path") + parser.add_argument("--config","-c", type=str, default="./config/prototype_of3.json", help="Configuration file path") + parser.add_argument("--cal","-cal", type=str, default="./config/dd01a_0.npy", help="Calibration file path") + parser.add_argument("--verbose","-verbose", type=bool, default=True, help="Print detailed information") + args = parser.parse_args() + # 步骤1:创建传感器对象 (必要步骤), # config_name为配置文件名(内置配置文件名)或路径(自定义配置文件路径) # cal_path为校准文件路径, 每一个设备需要创建/使用一个校准文件 # 方式1: 从视频文件读取 - sensor = orisys.Sensor("s1.mp4", config_name="./config/prototype_of3.json", cal_path= "./config/dd01a_0.npy", isstitch=True, cuda=True, verbose=True) + try: + input = int(args.video) + is_camera = True + sensor = orisys.Sensor(input, config_name=args.config, cal_path= args.cal, cuda=True, verbose=args.verbose) + except: + is_camera = False + sensor = orisys.Sensor(args.video, config_name=args.config, cal_path= args.cal, cuda=True, verbose=args.verbose) + + # sensor = orisys.Sensor(args.input, config_name=args.config, cal_path= args.cal, cuda=True, verbose=args.verbose) # 方式2: 从摄像头读取(取消注释使用) # sensor = orisys.Sensor(0, config_name="./config/prototype_of3.json", cal_path= "./config/cal.npy", isstitch=True, cuda=True, verbose=True) diff --git a/pulse_process.py b/pulse_process.py deleted file mode 100644 index 4e3ebb1..0000000 --- a/pulse_process.py +++ /dev/null @@ -1,308 +0,0 @@ -import os -os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" -import cv2 -import orisys -import numpy as np -import time - -import pyqtgraph as pg -from PyQt5.QtGui import QImage - -def create_plot(): - p1 = win.addPlot(title="法相") - p1.setLabel('bottom', '时间', units='s') - win.nextRow() - p2 = win.addPlot(title="切向 X") - p2.setLabel('bottom', '时间', units='s') - win.nextRow() - p3 = win.addPlot(title="切向 Y") - p3.setLabel('bottom', '时间', units='s') - plot_af = p1.plot(pen='w') - plot_tfx = p2.plot(pen='w') - plot_tfy = p3.plot(pen='w') - return p1, p2, p3, plot_af, plot_tfx, plot_tfy - -def update(plot_af, plot_tfx, plot_tfy, data, p1, p2, p3): - # global plot_af, plot_tfx, plot_tfy, data, p1, p2, p3 - plot_af.setData(data[:,:,0]) - p1.setYRange(min(data[:,1,0]),max(data[:,1,0])) - plot_tfx.setData(data[:,:,1]) - p2.setYRange(min(data[:,1,1]),max(data[:,1,1])) - plot_tfy.setData(data[:,:,2]) - p3.setYRange(min(data[:,1,2]),max(data[:,1,2])) - -def plot_to_frame(plot_widget, width=None, height=None): - """ - Convert pyqtgraph GraphicsLayoutWidget to numpy array (BGR format for OpenCV). - - Args: - plot_widget: pyqtgraph GraphicsLayoutWidget to capture - width: Optional width for output frame (default: widget width) - height: Optional height for output frame (default: widget height) - - Returns: - np.ndarray: BGR image array (height, width, 3) ready for cv2.VideoWriter - """ - # Get the widget size - if width is None: - width = plot_widget.width() - if height is None: - height = plot_widget.height() - - # Grab the widget as a QPixmap - pixmap = plot_widget.grab() - - # Resize if needed - if pixmap.width() != width or pixmap.height() != height: - pixmap = pixmap.scaled(width, height) - - # Convert QPixmap to QImage - qimage = pixmap.toImage() - - # Convert QImage to RGB format - qimage = qimage.convertToFormat(QImage.Format_RGB888) - - # Get image dimensions - img_width = qimage.width() - img_height = qimage.height() - - # Convert QImage to numpy array - # Use constBits() to get raw bytes, then reshape - buffer = qimage.constBits() - if buffer is not None: - arr = np.frombuffer(buffer, dtype=np.uint8).reshape((img_height, img_width, 3)) - else: - # Fallback: create empty array - arr = np.zeros((img_height, img_width, 3), dtype=np.uint8) - - # Convert RGB to BGR for OpenCV - bgr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR) - - return bgr - -# 深度图 -def draw_magnitude_map(vfield, threshold=3.0, colormap=cv2.COLORMAP_JET) -> np.ndarray: - """ - Draw color map of pointwise magnitude of velocity field. - - Args: - vfield: Velocity field to visualize (2D vector field) - threshold: Minimum magnitude to display (below threshold shows as zero/blue) - colormap: OpenCV colormap to use (default: COLORMAP_JET) - - Returns: - np.ndarray: 400x400 BGR image with colored magnitude map - """ - flow_field = vfield - - # Compute magnitude at each point - magnitude = np.sqrt(flow_field[:, :, 0]**2 + flow_field[:, :, 1]**2) - - # Smooth by downsampling and interpolating back - downsample_factor = 10 - h, w = magnitude.shape - magnitude_downsampled = magnitude[::downsample_factor, ::downsample_factor] - magnitude_smoothed = cv2.resize(magnitude_downsampled, (w, h), interpolation=cv2.INTER_CUBIC) - - # Apply threshold - set below-threshold values to zero - magnitude_thresholded = magnitude_smoothed.copy() - magnitude_thresholded[magnitude_smoothed < threshold] = 0 - - # Normalize to 0-255 range - mag_min, mag_max = magnitude_thresholded.min(), magnitude_thresholded.max() - if mag_max > mag_min: - magnitude_norm = ((magnitude_thresholded - mag_min) / (mag_max - mag_min) * 255).astype(np.uint8) - else: - magnitude_norm = np.zeros_like(magnitude_thresholded, dtype=np.uint8) - - # Apply colormap - magnitude_colored = cv2.applyColorMap(magnitude_norm, colormap) - - # Resize to output size - out_img = cv2.resize(magnitude_colored, (400, 400), interpolation=cv2.INTER_CUBIC) - - return out_img - -# 实时图表 -npoints = 100 # 图表数据长度 -pg.setConfigOption('background', 'k') -pg.setConfigOption('foreground', 'w') -app = pg.mkQApp("Plotting Example") -win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples") -win.setWindowTitle('pyqtgraph example: Plotting') -p1, p2, p3, plot_af, plot_tfx, plot_tfy = create_plot() -data = np.zeros([npoints,2,3]) -start_time = None # Will be set when sensor starts -for i in range(3): - data[:,0,i] = np.linspace(-npoints,0,npoints) - - - -def main(): - global start_time - - # 打开视频 - # sensor1 = Sensor("./s1.mp4",isstitch=True,cuda = True,verbose = True) - # 方式1: 从视频文件读取 - sensor1 = orisys.Sensor("./data/pulse_test_1.mp4", config_name="./config/prototype_of3.json", cal_path= "./config/dd01a_98.npy", isstitch=True, cuda=False, verbose=True) - - start_time = time.time() - is_save = True - frame_count = 0 - writer = None - save_path = None - - # Video writer for plot - is_save_plot = True - plot_writer = None - plot_frame_count = 0 - plot_save_path = None - - if True: - save_path = f"./data/pulse_test_arrows.mp4" - # Ensure directory exists - os.makedirs(os.path.dirname(save_path), exist_ok=True) - # Get frame dimensions from the actual frame - save_img = sensor1.frame - - # if save_img is not None: - if True: - # Convert grayscale (1 channel) to BGR (3 channels) if needed for dimension check - # temp_img = save_img.copy() - # if len(temp_img.shape) == 2 or (len(temp_img.shape) == 3 and temp_img.shape[2] == 1): - # if len(temp_img.shape) == 3: - # temp_img = temp_img.squeeze(2) - # temp_img = cv2.cvtColor(temp_img, cv2.COLOR_GRAY2BGR) - - # frame_height, frame_width = temp_img.shape[:2] - frame_height = 400 - frame_width = 400 - writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), 30, (frame_width, frame_height)) - if writer.isOpened(): - is_save = True - print(f"Start writing to {save_path}") - else: - print(f"Failed to open video writer for {save_path}") - writer = None - - # Setup video writer for plot - plot_save_path = f"./data/pulse_test_plot.mp4" - os.makedirs(os.path.dirname(plot_save_path), exist_ok=True) - # Get plot widget dimensions - plot_width = win.width() - plot_height = win.height() - # Use fixed dimensions if widget not yet rendered - if plot_width <= 0 or plot_height <= 0: - plot_width = 800 - plot_height = 600 - - plot_writer = cv2.VideoWriter(plot_save_path, cv2.VideoWriter_fourcc(*'mp4v'), 30, (plot_width, plot_height)) - if plot_writer.isOpened(): - is_save_plot = True - print(f"Start writing plot to {plot_save_path}") - else: - print(f"Failed to open plot video writer for {plot_save_path}") - plot_writer = None - - - while True: - sensor1.get_img() # 提取并拼接复眼图像 - - - - sensor1.compute_deformation() # 计算形变和分解 - sensor1.compute_contact() - # 提取信息: - # 向量场: VRAW, VNORMAL, VSHEAR: 原始,法向,切向 - # 帧率,时间戳: FPS, TIMESTAMP - # 变形量总和: FNORMAL , FSHEARX, FSHEARY : 法向,切向(X),切向(Y) - fps, fn, fx, fy, flow, contour, centroid, depth_map = sensor1.read_info(sensor1.info.FPS, sensor1.info.FNORMAL, sensor1.info.FSHEARX, sensor1.info.FSHEARY, sensor1.info.VRAW,sensor1.info.CONTOUR, - sensor1.info.CENTROID, - sensor1.info.DEPTH) - print(fps,fn,fx,fy) - - - # 显示触碰点和区域 # 步骤 3.2: 在图像上触碰点和区域 - white_img = np.full_like(sensor1.img, 0) - - image_with_foe = orisys.util.draw_contact(white_img, contour, centroid,region_color=(255, 255, 255)) - cv2.imshow("Contact Centroid", image_with_foe) - # 显示向量场 - - arrows = orisys.util.draw_arrows(white_img, flow, threshold=5, grid_spacing=10, arrow_scale=1.0) - cv2.imshow("Deformation", arrows) - # 显示深度图 - mag = draw_magnitude_map(flow) - cv2.imshow("Magnitude", mag) - - if is_save and writer is not None and sensor1.frame is not None: - frame_count += 1 - save_img = arrows - - # Convert grayscale (1 channel) to BGR (3 channels) if needed - if len(save_img.shape) == 2 or (len(save_img.shape) == 3 and save_img.shape[2] == 1): - if len(save_img.shape) == 3: - save_img = save_img.squeeze(2) - save_img = cv2.cvtColor(save_img, cv2.COLOR_GRAY2BGR) - - writer.write(save_img) - - # 显示图表 - 更新时间轴 - current_time = time.time() - start_time - for i in range(3): - data[:-1, 0, i] = data[1:, 0, i] # shift time data left - data[:-1, 1, i] = data[1:, 1, i] # shift force data left - data[-1, 0, :] = current_time # set current time for all plots - data[-1, 1, 0] = fn - data[-1, 1, 1] = fx - data[-1, 1, 2] = fy - update(plot_af, plot_tfx, plot_tfy, data, p1, p2, p3) # Update plot - - # Capture and save plot frame - if is_save_plot and plot_writer is not None: - plot_frame_count += 1 - # Process Qt events to ensure plot is updated - app.processEvents() - # Capture plot as frame - plot_frame = plot_to_frame(win) - plot_writer.write(plot_frame) - - key = cv2.waitKey(1) - if key & 0xFF == ord("q"): - print("\n 停止程序") - break - - if key == ord("e"): - is_save = False - if writer is not None: - writer.release() - writer = None - if save_path is not None: - print(f"Stop writing to {save_path}, total frames: {frame_count}") - else: - print(f"Stop writing, total frames: {frame_count}") - frame_count = 0 - - # Stop plot recording - is_save_plot = False - if plot_writer is not None: - plot_writer.release() - plot_writer = None - if plot_save_path is not None: - print(f"Stop writing plot to {plot_save_path}, total frames: {plot_frame_count}") - else: - print(f"Stop writing plot, total frames: {plot_frame_count}") - plot_frame_count = 0 - - sensor1.disconnect() # 断开传感器连接 - - # Clean up video writers - if writer is not None: - writer.release() - if plot_writer is not None: - plot_writer.release() - print(f"Plot video saved: {plot_save_path}, total frames: {plot_frame_count}") - - -if __name__ == '__main__': - main() diff --git a/src/orisys/HHD.py b/src/orisys/HHD.py index 8604d80..0e1c58a 100644 --- a/src/orisys/HHD.py +++ b/src/orisys/HHD.py @@ -51,7 +51,8 @@ class nHHD(object): backend = 'cpu' elif backend == 'opencl' and not OPENCL_AVAILABLE: backend = 'cpu' - + + self.debug = kwargs.get('debug', False) self.poisson_backend = backend # Initialize CUDA streams for cupy backend @@ -103,7 +104,7 @@ class nHHD(object): Dispatch Poisson solve using the selected backend. """ if self.poisson_backend == 'opencl': - return self.psolver.solve_opencl(field, verbose, profile=True) + return self.psolver.solve_opencl(field, verbose, profile=self.debug) if self.poisson_backend == 'cupy': return self.psolver.solve_gpu(field, verbose) @@ -136,7 +137,7 @@ class nHHD(object): # compute potentials if self.poisson_backend == 'opencl' and ('OPENCL_AVAILABLE' in globals() and OPENCL_AVAILABLE): - self.nD, self.nRu = self.psolver.solve_opencl_batch2(self.div, self.curlw, verbose > 0, profile=True) + self.nD, self.nRu = self.psolver.solve_opencl_batch2(self.div, self.curlw, verbose > 0, profile=self.debug) else: self.nD = self._solve_poisson_backend(self.div, verbose > 0) self.nRu = self._solve_poisson_backend(self.curlw, verbose > 0) @@ -1655,7 +1656,7 @@ class PoissonSolver(object): return full_conv[slices] - def solve_opencl_batch2(self, f1, f2, verbose=False, profile=True): + def solve_opencl_batch2(self, f1, f2, verbose=False, profile=False): """ Batched OpenCL Poisson solver for two 2D fields. Optimized: scatter H2D (~4x smaller transfer), fused multiply diff --git a/src/orisys/sensor.py b/src/orisys/sensor.py index b705727..cfce9d1 100644 --- a/src/orisys/sensor.py +++ b/src/orisys/sensor.py @@ -59,7 +59,7 @@ class Sensor: # Initialization # ========================================================================= - def __init__(self, vid_src, cuda=True, isstitch=True, verbose=True, + def __init__(self, vid_src, cuda=True, isstitch=True, verbose=True, debug=False, config_name="prototype", cal_path=None, override=False, box=None, mirror=True, poisson_backend='auto'): """ Initialize Sensor with video source and configuration. @@ -86,6 +86,7 @@ class Sensor: self.isstitch = isstitch self.cuda = cuda self.verbose = verbose + self.debug = debug self.config_name = config_name self.cal_path = cal_path self.mirror = mirror @@ -130,7 +131,7 @@ class Sensor: # Initialize components self.stitcher = Stitcher(isstitch=isstitch, config_stitcher=self.__config_stitcher, verbose=self.verbose) - self.__tracker = Tracker(adaptive=True, cuda=cuda, verbose=self.verbose, dis_preset=self._config_dis_preset, config_tracker=self.__config_tracker) + self.__tracker = Tracker(adaptive=True, cuda=cuda, verbose=self.verbose, config_tracker=self.__config_tracker) self.__contact3d = Contact3D(config_contact=self.__config_contact, verbose=self.verbose) self.__hhd = None self.poisson_backend = poisson_backend @@ -252,7 +253,7 @@ class Sensor: # Initialize HHD solver if self.__hhd is None: - self.__hhd = nHHD(cuda=self.cuda, grid=flow.shape[:2], spacings=(18, 18), poisson_backend=self.poisson_backend) + self.__hhd = nHHD(cuda=self.cuda, grid=flow.shape[:2], spacings=(18, 18), poisson_backend=self.poisson_backend, debug=self.debug) # Force computation # When OpenCL backend is selected, use host force path to ensure diff --git a/src/orisys/tracker.py b/src/orisys/tracker.py index bc484da..1df704a 100644 --- a/src/orisys/tracker.py +++ b/src/orisys/tracker.py @@ -11,7 +11,7 @@ class Tracker: lower_threshold=12.0, upper_threshold=18.0, loop_closure_interval=1, - dis_preset='medium', + # dis_preset='medium', verbose=False): # Check if OpenCV is built with CUDA support # if cuda and not cv2.cuda.getCudaEnabledDeviceCount():