基于官方132GS MIPI双目相机图像原始分辨率旋转后存在黑边

官方相机原始分辨率支持的是1088*1280,但是旋转存在黑边,请技术人员帮忙看一下为什么会存在黑边,运行脚本mipi_cam_dual_channel_websocket_132gs_nocal+cal+r90.launch.py,通过修改长宽和旋转角度获取旋转前的图像和旋转后的图像,

旋转前Image Resolution: Width = 1088, Height = 2560

— Black Border Analysis —

Top: 0 rows
Bottom: 0 rows
Left: 0 columns
Right: 0 columns

旋转后:Image Resolution: Width = 1280, Height = 2176

— Black Border Analysis —

Top: 1 rows
Bottom: 2 rows
Left: 3 columns
Right: 0 columns

核心结论

黑边产生的根本原因:硬件输出分辨率与软件旋转后的目标分辨率不匹配,且未进行正确的裁剪(Crop)或填充(Pad)处理。

  1. 物理限制:SC132 传感器的原生有效像素为 1088 (H) x 1280 (V)

  2. 旋转逻辑

    • 旋转前:图像数据通常以 Sensor 原生方向输出,即 Width=1088, Height=1280(或者根据你的脚本配置,可能是双路拼接或其他模式,但你提供的 raw_test_rdk1 显示为 1088x2560,这暗示可能涉及双摄像头拼接或垂直堆叠,需确认是否为双摄模式。若为单摄,1088x1280 是标准值)。

    • 旋转 90 度后:图像的宽高互换。理论上,1088x1280 的图像旋转 90 度后,应变为 1280 (W) x 1088 (H)

  3. 异常数据分析

    • 你提到的旋转后分辨率为 Width = 1280, Height = 2176

    • 注意2176 恰好是 1088 * 2。这说明你的脚本 mipi_cam_dual_channel... 很可能是在处理双摄像头数据,或者将两帧图像在垂直方向进行了拼接/处理。

    • 如果单看单目相机:旋转后应为 1280x1088。如果你期望得到正方形的 1280x1280 或者保持某种比例,多出来的部分或少掉的部分就会表现为“黑边”或“拉伸”。

    • 黑边来源:VPS(视觉处理子系统)或 ISP 在进行旋转操作时,如果输出的 Buffer 尺寸(Target Resolution)大于旋转后的有效图像尺寸,多余区域默认填充为黑色(0值)。例如,若设置输出为 1280x1280,而旋转后有效图像仅为 1280x1088,则上下各会有 (1280-1088)/2 = 96 像素的黑边。但你检测到的黑边很小(Top 1, Bottom 2…),这通常意味着对齐问题ISP/VPS 的内存行对齐(Stride)要求导致的微小偏移。

排查与解决步骤

请按照以下优先级进行排查和修改

1. 确认 Launch 文件中的分辨率配置

检查 mipi_cam_dual_channel_websocket_132gs_nocal+cal+r90.launch.py 文件中关于 VPS 或图像处理的节点配置。

  • 关键点:查找是否有 width, height, target_width, target_height 等参数。
  • 问题推测:脚本可能在旋转后,强行将输出分辨率设定为了一个非自然旋转结果的尺寸(例如强制输出 1280x1280 或 1280x2176),导致边缘填充黑边。

建议操作:
确保旋转后的输出分辨率严格等于旋转前的宽高互换值。

  • 若输入是 1088x1280,旋转 90 度后,输出应设为 1280x1088
  • 若输入是双摄拼接 1088x2560,旋转 90 度后,输出应设为 2560x1088

2. 检查 VPS/ISP 的对齐要求 (Stride Alignment)

RDK 平台的 VPS 模块对图像宽度的内存对齐有严格要求(通常要求宽度是 16 或 32 的倍数)。

  • 现象解释:你检测到的黑边非常小(Top:1, Bottom:2, Left:3),这极有可能是因为实际写入 Buffer 的数据起始位置与 Buffer 理论起始位置存在几个像素的对齐偏移,或者是 ISP 输出的有效区域(Valid Region)与 Buffer 大小不完全重合。
  • SC132 特性:SC132 是 1280x1088 (或 1088x1280 取决于安装方向)。1088 和 1280 都是 16/32 的倍数,理论上对齐没问题。但如果经过了 Crop 或 Resize,可能会引入非对齐尺寸。

建议操作:
在代码中获取图像后,不要直接使用整个 Buffer,而是根据 ISP/VPS 返回的 有效区域信息(Valid Rect) 进行裁剪。

# 伪代码示例:获取图像后,根据有效区域裁剪
img = vps_node.get_img()
# 假设 img 包含 metadata 指示有效区域
valid_x, valid_y, valid_w, valid_h = img.get_valid_rect() 

# 如果存在黑边,手动裁剪掉
if valid_w > 0 and valid_h > 0:
    cropped_img = img[valid_y:valid_y+valid_h, valid_x:valid_x+valid_w]
else:
    # 如果没有 metadata,尝试根据已知黑边数量裁剪
    # 根据你的分析:Left 3, Top 1, Right 0, Bottom 2
    cropped_img = img[1:-2, 3:] 

3. 验证传感器原生方向与旋转定义

  • 确认 Sensor 安装方向:SC132 模组在 RDK 开发板上通常是竖直安装还是水平安装?
    • 如果 Sensor 原生输出是 1088(W)x1280(H),那么“无旋转”状态下就是竖长图。
    • 如果应用层期望横屏(1280x1088),则需要旋转 90 度。
  • 检查 r90 参数:Launch 文件名中的 r90 代表 Rotate 90。请确认该旋转是在 ISP 阶段 完成,还是在 VPS 阶段 完成,亦或是在 应用层(OpenCV) 完成。
    • ISP/VPS 旋转:效率高,但受硬件对齐限制,容易产生微量黑边或需要特定配置去除黑边。
    • 应用层旋转:使用 OpenCV cv2.rotate(),可以完美控制,无黑边,但占用 CPU。

建议方案(推荐应用层后期处理以消除黑边):
如果硬件旋转难以消除微量黑边,建议在接收到图像后,使用软件方式二次处理:

import cv2
import numpy as np

def remove_black_borders_and_rotate(image):
    """
    假设 image 是旋转后的图像,存在微量黑边
    """
    # 1. 转换为灰度图寻找非零区域(有效图像区域)
    if len(image.shape) == 3:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    else:
        gray = image
        
    # 2. 阈值二值化,找到有效像素
    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)
    
    # 3. 查找轮廓或非零坐标
    coords = cv2.findNonZero(thresh)
    
    if coords is not None:
        x, y, w, h = cv2.boundingRect(coords)
        # 4. 裁剪出有效图像
        cropped = image[y:y+h, x:x+w]
        return cropped
    else:
        return image # 全黑情况

通过官方的mipi_cam的节点保存的图像,目前定位到旋转是通过GDC模块实现的,/home/hobot_mipi_cam/src/x5/hobot_mipi_cap_iml.cpp中的gen_gdc_bin_rotation()函数生成旋转矩阵,目前运行的是系统自带的节点,通过修改脚本的参数完成是否旋转的功能,期望旋转后的分辨率是1280*1088,不进行任何裁减。

有以下几种方案可以尝试一下:

1. 修改 GDC 旋转配置 (gen_gdc_bin_rotation)

你提到的 gen_gdc_bin_rotation() 函数负责生成 GDC 的任务描述符。要消除黑边,必须确保 GDC 的输出裁剪(Crop)输出尺寸设置 精确匹配有效像素。

方案 A:调整 GDC 输出尺寸(推荐)

在生成 GDC bin 时,确保 dst_wdst_h 严格等于旋转后的有效像素,而不是对齐后的内存尺寸。

  • 逻辑修正
    1. 获取 Sensor 的有效宽高:src_w = 1088, src_h = 1280
    2. 旋转 90 度后,目标宽高应为:dst_w = 1280, dst_h = 1088
    3. 关键点:检查 GDC 配置结构中是否有 croprect 字段。如果有,确保源裁剪区域(Src Rect)只包含有效像素,不包含 Blanking 区。

方案 B:在 GDC 后增加软件裁剪(最稳妥)

由于硬件 GDC 的对齐限制(通常要求宽度 16/32/64 字节对齐),直接通过 GDC 去除几个像素的黑边可能比较困难(取决于具体 GDC 版本支持的粒度)。最简单的工程化做法是在 GDC 输出后,立即进行内存拷贝裁剪。

hobot_mipi_cam 的代码流程中,找到 GDC 回调或数据导出位置:

// 假设 gdc_output_img 是 GDC 旋转后的图像
// 已知黑边:Top 1, Bottom 2, Left 3, Right 0
// 期望输出:1280 x 1088

cv::Mat rotated_img = ...; // 从 GDC buffer 转换来的 cv::Mat

// 执行裁剪
// range: [y_start, y_end), [x_start, x_end)
cv::Mat clean_img = rotated_img(cv::Rect(3, 1, 1280 - 3, 1088 - 1 - 2));

// 注意:如果 rotated_img 的总尺寸不是 1280x1088+黑边,而是更大的对齐尺寸
// 你需要先确认 rotated_img 的实际 size。
// 根据你的描述,旋转后分辨率显示为 1280x2176 (双摄?) 或单摄的某个值。
// 如果是单摄 1280x1088 期望值,但实际有黑边,说明实际 Buffer 可能是 1280x1091 或类似。

2. 针对 mipi_cam_dual_channel 的特殊处理

你使用的是双通道脚本。SC132GS 双摄通常是两个独立的 Sensor。

  • 现象回顾:旋转前 1088x2560,旋转后 1280x2176
  • 分析
    • 2560 = 1280 * 2。说明旋转前是两个 1088x1280 的图像左右拼接(Width 1088+1088? 不对,1088+1088=2176。这里是 1088x2560,说明是 上下拼接?即 Width 1088, Height 1280+1280=2560)。
    • 如果旋转前是 1088(W) x 2560(H),旋转 90 度后,理论上应该是 2560(W) x 1088(H)
    • 但你得到的旋转后是 1280(W) x 2176(H)。这说明脚本内部可能先对单目图像进行了旋转,然后再拼接,或者使用了不同的拼接逻辑。
    • 2176 = 1088 * 2。这说明旋转后的单目图像高度是 1088,宽度是 1280。然后两个图像上下拼接成了 1280x2176。

黑边来源锁定
在单目旋转阶段,每个 1088x1280 的图像旋转成 1280x1088 时,产生了微量黑边。当两个这样的图像上下拼接时,黑边被保留在了最终图像的顶部、底部和中间接缝处(如果左右有黑边,拼接后会在左侧或右侧)。

解决方法
gen_gdc_bin_rotation 中,针对单目图像进行处理,而不是处理拼接后的图像。

  1. 修改 GDC 输入:确保传入 GDC 的是单个 Sensor 的数据。
  2. 精确设置 GDC 输出
    • Source: 1088 x 1280
    • Rotate: 90 deg
    • Destination: 1280 x 1088
  3. 如果 GDC 无法完美对齐
    在代码中,对每个通道的图像分别进行裁剪,然后再拼接。
# Python 端伪代码(如果在 ROS 节点后处理)
def process_dual_cam(img_left_raw, img_right_raw):
    # 假设已经过 GDC 旋转,但存在黑边
    # 左图: 1280x1088 + 黑边 -> 裁剪为纯 1280x1088
    left_clean = crop_black_borders(img_left_raw) 
    right_clean = crop_black_borders(img_right_raw)
    
    # 重新拼接
    final_img = np.vstack((left_clean, right_clean)) # 如果是上下拼接
    return final_img

3. 官方参数调整建议(无需改代码)

尝试在 Launch 文件中寻找是否有关于 ISP CropVPS Crop 的参数。

  • 检查 mipi_cam.launch.py 或对应的 yaml 配置文件。
  • 查找关键词:roi, crop, offset_x, offset_y
  • 原理:如果在 ISP 阶段就裁剪掉 Sensor 输出的无效行/列(即那 1-3 个像素的黑边源头),那么送给 GDC 的就是纯净的 1088x1280 数据,GDC 旋转后自然就是纯净的 1280x1088。

操作建议
查阅 SC132GS 的 Datasheet,确认其 Active Pixel 的确切起始坐标。如果 Datasheet 说明有效像素从 (3, 1) 开始,那么在 ISP 配置中设置 crop_x=3, crop_y=1, crop_w=1088, crop_h=1280(注意宽高对应关系)。

:white_check_mark: 总结行动指南

  1. 首选方案(软件裁剪):鉴于黑边像素固定且微小(Top 1, Bottom 2, Left 3),在接收到图像后,直接使用 OpenCV 或 NumPy 进行固定坐标裁剪。这是最快、最不影响帧率的方法。

    # 对于单目旋转后的图像 (假设原始 Buffer 包含黑边)
    # 请根据实际打印的 img.shape 调整切片索引
    img_clean = img[1:-2, 3:] 
    
  2. 次选方案(ISP 配置):检查 mipi_cam 的配置文件,看是否能配置 ISP 的 ROI(Region of Interest),在源头切除黑边。

  3. 底层方案(GDC 修改):如果必须修改 C++ 代码,请在 gen_gdc_bin_rotation 中检查 hb_vgs_rotation_ex 或类似 GDC API 的调用参数,确保 dst_rect 严格匹配有效像素,并确认源地址指针是否偏移到了有效像素起始处。

看起来黑边处理很麻烦,想确认一下基于旋转后带黑边的图进行标定,进行GDC校正和去畸变的话,这组参数可用吗

不可用(或者说极不推荐),直接使用带黑边的图像进行标定会导致严重的几何畸变校正误差。