VisionG 模块与硬件控制
一份面向 VisionG 开发板的 Python API 参考与硬件控制指南,旨在提供简洁、易用的视觉处理与底层硬件交互接口。
1. 简介
visiong 模块是一个为VisionG开发板设计的Python 接口库。通过封装底层的硬件加速器,如 ISP、RGA、IVE 和 NPU,为开发者提供了简单易用的视觉处理函数。“通用硬件接口控制”部分参考了幸狐的文档,介绍了如何使用标准 Python 库来操作 GPIO、I2C 等常用外设。
注意: 本项目为个人开发,受能力所限,可能存在性能优化不足及潜在 Bug。对于生产环境或要求高性能的场景,仍建议使用 C/C++ 进行开发,禁止商用。
2. 核心类概览
visiong.ImageBuffer: 存储和操作图像数据的核心对象。所有图像处理的起点和终点。visiong.Camera: 控制和从摄像头捕获图像的接口。visiong.NPU: 加载 AI 模型并执行硬件加速推理的接口。visiong.IVE: 访问底层 IVE (智能视频引擎) 硬件加速功能的接口。visiong.DisplayFB: 将图像显示到 Framebuffer(屏幕)的接口。visiong.DisplayUDP: 将图像编码并通过 UDP 网络流传输的接口。visiong.DisplayHTTP: 启动一个 MJPEG-over-HTTP 网络流服务器的接口。visiong.Touch: 与 I2C 触摸屏交互的接口。- 数据对象:
Blob,Line,Circle,QRCode,Detection等,用于承载分析函数返回的结果。
3. `visiong.ImageBuffer` 类
ImageBuffer 是 visiong 库中数据流转的中心。无论是从摄像头捕获、进行 RGA/IVE 处理,还是作为 NPU 的输入,都是通过这个对象来完成。它智能地管理着硬件(Zero-copy)内存和常规 CPU 内存。
静态方法
visiong.ImageBuffer.create(width, height, format, color=(0,0,0))
创建一个由指定颜色填充的新图像。此函数利用 RGA 硬件进行快速填充,返回一个基于 CPU 内存的 ImageBuffer 对象。
width(int): 新图像的宽度。height(int): 新图像的高度。format(str): 图像的像素格式。支持的别名包括:"rgb","bgr","rgba","bgra","yuv","gray"。完整格式列表:"rgb888","bgr888","rgba8888","bgra8888","rgb565","bgr565","yuv420sp","gray8"。color(tuple, optional): 填充颜色,可以是(R, G, B)或(R, G, B, A)元组,默认为黑色。- 返回: 一个新创建的
ImageBuffer对象。
# 创建一个 128x128 的蓝色 BGR 图像
blue_img = visiong.ImageBuffer.create(128, 128, "bgr", color=(255, 0, 0))
visiong.ImageBuffer.load(filepath: str) -> visiong.ImageBuffer
从文件加载图像(支持JPEG, PNG, BMP等)。此函数会自动将图像尺寸调整为硬件(RGA)友好的对齐尺寸(宽度16字节对齐,高度2字节对齐),以确保后续硬件操作的兼容性和性能。
filepath(str): 图像文件的路径。- 返回: 一个新的、尺寸已对齐的
ImageBuffer对象。
属性与Numpy接口
img.width -> int
图像的宽度(像素),只读。
img.height -> int
图像的高度(像素),只读。
img.format -> str
图像的像素格式字符串, 例如 "BGR888", "YUV420SP", "GRAY8", "S16C1",只读。
img.data -> bytes
返回图像像素数据的紧密排列(无 stride)的拷贝。
此属性非常有用,因为它提供了一个标准的、可预测的字节序列,方便您将其用于其他 Python 库(如 Pillow)或直接写入文件。无论原始 ImageBuffer 是 Zero-copy 的硬件缓冲还是带 stride 的 CPU 缓冲,此属性总能返回一份干净的、连续的内存拷贝。
与 NumPy 的交互
ImageBuffer 与 NumPy 库可以进行高效的零拷贝或低拷贝交互。
np.asarray(img): 对于BGR888,RGB888,GRAY8格式,此操作可以实现零拷贝转换,直接创建一个共享内存的 NumPy 数组。这是最高效的方式。img.to_numpy(copy=False): 自动将图像(如果需要)硬件转换为BGR888格式,然后返回一个零拷贝的 NumPy 数组视图。visiong.ImageBuffer.from_numpy(array, format): 从一个 NumPy 数组创建一个ImageBuffer。如果数组尺寸不符合硬件对齐要求,会自动进行 padding 处理。
import visiong
import numpy as np
import cv2
cam = visiong.Camera(640, 360, "bgr")
# 零拷贝转换为 NumPy 数组
img_buffer = cam.snapshot()
np_array = np.asarray(img_buffer)
# 使用 OpenCV 进行处理
gray_np = cv2.cvtColor(np_array, cv2.COLOR_BGR2GRAY)
blurred_np = cv2.GaussianBlur(gray_np, (5, 5), 0)
# 从 NumPy 数组创建回 ImageBuffer
blurred_img_buffer = visiong.ImageBuffer.from_numpy(blurred_np, "gray")
cam.release()
核心方法
img.is_valid() -> bool
检查图像缓冲区是否包含有效数据。 在对 ImageBuffer 进行任何操作之前,建议先调用此方法进行检查,特别是对于从 camera.snapshot() 等可能失败的操作中获取的 ImageBuffer。
img.copy() -> visiong.ImageBuffer
创建并返回当前 ImageBuffer 的一个深拷贝。 此方法会创建一个全新的 ImageBuffer 对象,其像素数据与原始对象完全相同,但二者在内存上完全独立。后续对拷贝后对象的任何修改(包括原地绘制)都不会影响原始对象。
img.save(filepath: str, quality: int = 75) -> None
使用硬件JPEG编码器将图像保存到文件。速度远快于纯软件实现。
filepath(str): 保存的文件路径。quality(int, optional): JPEG 压缩质量 (1-100),默认为75。
转换与变换方法 (硬件加速)
这些方法利用 RGA 硬件进行加速,并且总是返回一个新的 ImageBuffer 对象,原始对象不会被修改。
img.to_format(new_format: str) -> visiong.ImageBuffer
将图像转换为指定的像素格式。例如,将摄像头采集的 YUV420SP 格式转换为 NPU 或绘图所需的 BGR888 格式。
img.to_grayscale() -> visiong.ImageBuffer
将图像转换为灰度图。这是 img.to_format("gray8") 的便捷写法。
img.resize(new_width: int, new_height: int) -> visiong.ImageBuffer
将图像缩放至指定的新的宽度和高度。
img.crop(rect: tuple | x: int, y: int, w: int, h: int) -> visiong.ImageBuffer
从图像中裁剪出一个矩形区域。可以接受一个 (x, y, w, h) 元组或四个独立的整数参数。
img.rotate(angle_degrees: int) -> visiong.ImageBuffer
将图像旋转 90、180 或 270 度。
img.flip(horizontal: bool, vertical: bool) -> visiong.ImageBuffer
水平和/或垂直翻转图像。
img.letterbox(target_width, target_height, color=(128,128,128)) -> visiong.ImageBuffer
将图像进行 letterbox(信箱模式)变换。此操作会保持图像的原始宽高比,将其等比缩放至目标尺寸之内,并在空白区域填充指定的颜色。这在将任意尺寸的图像喂给固定尺寸输入的 AI 模型(如 YOLOv5)时非常有用。
img.warp_perspective(quad, out_width, out_height) -> visiong.ImageBuffer
执行透视变换。 此方法将源图像中的一个四边形区域(由四个角点定义)变换为一个指定尺寸的矩形输出图像。常用于校正倾斜的物体,例如将拍摄到的倾斜的二维码或A4纸张“扶正”。
quad(list[tuple[int, int]]): 一个包含四个(x, y)元组的列表,代表源图像中四边形的四个角点。顺序应为:左上、右上、右下、左下。out_width(int): 目标矩形图像的宽度。out_height(int): 目标矩形图像的高度。- 返回: 一个包含变换结果的新的
ImageBuffer对象。
# 假设我们找到了一个四边形的四个角点
corners = [(100, 50), (300, 60), (290, 250), (90, 240)] # 左上, 右上, 右下, 左下
# 把它变换成一个 200x200 的正视图
warped_img = img.warp_perspective(corners, 200, 200)
原地绘制方法
这些方法会直接修改 ImageBuffer 自身的像素数据,并返回该对象自身的引用,从而支持链式调用 (e.g., img.draw_rectangle(...).draw_string(...))。
重要警告: 如果ImageBuffer的格式不是可绘制的彩色格式(如YUV420SP或GRAY8),在调用任何绘制方法时,该ImageBuffer将被自动、原地转换为BGR888格式。这意味着原始的 YUV 或灰度数据将会丢失。如果您需要保留原始数据,请务必先调用img.copy()创建副本再进行绘制。
img.draw_line(x0, y0, x1, y1, color=(255,255,255), thickness=1)
在图像上原地绘制一条线段。
img.draw_rectangle(rect | x, y, w, h, color=(255,255,255), thickness=1, fill=False)
在图像上原地绘制一个矩形。
img.draw_circle(cx, cy, radius, color=(255,255,255), thickness=1, fill=False)
在图像上原地绘制一个圆。
img.draw_string(x, y, text, color=(255,255,255), scale=1.0, thickness=1)
在图像上原地绘制文本。
img.draw_cross(cx, cy, color=(255,255,255), size=5, thickness=1)
在图像上原地绘制一个十字标记。
img.paste(img_to_paste, x, y)
将另一个 ImageBuffer 粘贴到当前图像的指定位置。此操作利用 RGA 硬件加速,并返回自身引用以支持链式调用。
img.blend(img_to_blend, x=0, y=0)
使用 Alpha 通道将一个 RGBA 格式的 ImageBuffer 混合(blend)到当前图像的指定位置。这是一个 CPU 操作,并返回自身引用以支持链式调用。
图像分析方法
这些方法利用内部的图像处理算法(通常基于OpenCV)从图像中提取特征。这些方法可能会自动对图像进行预处理(如转换为灰度图)。
img.find_blobs(thresholds_lab, invert=False, roi=(0,0,0,0), x_stride=2, y_stride=2, area_threshold=10, pixels_threshold=10, merge=True, margin=10) -> list[visiong.Blob]
根据 LAB 颜色空间中的阈值查找并返回色块(blobs)列表。
thresholds_lab(list[tuple]): 必需参数。一个包含一个或多个阈值元组的列表。每个元组格式为(L_min, L_max, A_min, A_max, B_min, B_max),定义了要寻找的颜色范围。invert(bool, optional): 是否反转阈值范围,默认为False。roi(tuple, optional): 感兴趣区域(x, y, w, h),默认为全图。x_stride/y_stride(int, optional): 搜索时的像素步长。增加此值可提速,但可能漏掉小色块。area_threshold(int, optional): 色块的最小面积(像素总数)。用于过滤掉微小的噪点。pixels_threshold(int, optional): 色块包含的像素点数阈值,功能类似area_threshold。merge(bool, optional): 是否合并位置相近且颜色相似的色块。margin(int, optional): 合并时允许的最大间距。- 返回:
visiong.Blob对象列表。
img.binarize(method="otsu", threshold_range=(128, 255), invert=False, adaptive_block_size=11, adaptive_c=2, pre_blur_kernel_size=0, post_morph_kernel_size=0) -> visiong.ImageBuffer
使用指定方法对图像进行二值化处理。 输入图像会自动转换为灰度图进行处理。
method(str, optional): 二值化方法,可选:"manual": 手动阈值模式,使用threshold_range参数"otsu": 大津法自动阈值 (默认)"adaptive_mean": 自适应均值阈值"adaptive_gaussian": 自适应高斯阈值
threshold_range(tuple[int, int], optional): 手动模式下的阈值范围(min, max),默认为(128, 255)。invert(bool, optional): 是否反转黑白像素,默认为False。adaptive_block_size(int, optional): 自适应方法的块大小(必须为奇数且大于1),默认为11。adaptive_c(int, optional): 自适应方法的常数C,默认为2。pre_blur_kernel_size(int, optional): 二值化前的高斯模糊核大小。设为0禁用,必须为奇数且≥3(如3,5,7)。核越大模糊效果越强,默认为0(关闭)。post_morph_kernel_size(int, optional): 二值化后的形态学开运算核大小。设为0禁用,必须为整数≥2(如3,5,7)。核越大去除的噪声越大,默认为0(关闭)。- 返回: 一个新的二值化后的
ImageBuffer对象,格式为GRAY8。
# 使用大津法自动二值化
binary_img = img.binarize(method="otsu")
# 使用自适应高斯阈值,带预处理和后处理
binary_img = img.binarize(
method="adaptive_gaussian",
adaptive_block_size=15,
adaptive_c=3,
pre_blur_kernel_size=5,
post_morph_kernel_size=3
)
img.find_squares(roi=(0,0,0,0), threshold_val, min_area=500, approx_epsilon=0.02, corner_sample_radius=10, corner_ratio_thresh=2.0, edge_check_offset=5, area_sample_points=50, area_white_thresh=0.90, area_morph_close_kernel_size=0, duplicate_center_thresh=20.0, duplicate_area_thresh=0.2) -> list[list[tuple[int, int]]]
使用一种基于角点验证的稳健算法在图像中查找正方形。 该方法比 find_polygons 计算量更大,但对于在嘈杂或复杂背景中查找类似正方形的物体具有更高的准确性。
roi(tuple): 感兴趣区域(x, y, w, h)。threshold_val(int): 用于二值化的灰度阈值。比此值暗的像素将成为目标。min_area(int, optional): 要考虑的最小轮廓面积。approx_epsilon(float, optional): 多边形逼近的精度因子。corner_sample_radius(int, optional): 用于在潜在角点周围采样以验证它们的半径。corner_ratio_thresh(float, optional): 有效角点所需的前景与背景像素的最小比率。edge_check_offset(int, optional): 用于检查边缘是否在对象外部的偏移距离。area_sample_points(int, optional): 在候选正方形内部采样的随机点数。area_white_thresh(float, optional): 有效正方形内部所需的前景像素的最小比率。area_morph_close_kernel_size(int, optional): 验证正方形内部区域前,用于填充内部空洞的形态学闭运算核大小。设为0禁用。duplicate_center_thresh(float, optional): 判定为重复正方形的最大中心距离。duplicate_area_thresh(float, optional): 判定为重复正方形的最大相对面积差异。- 返回: 一个已找到的正方形列表,其中每个正方形是包含四个
(x, y)顶点元组的列表。
img.find_lines(roi=(0,0,0,0), x_stride=1, y_stride=1, threshold=60, rho_resolution_px=1.0, theta_resolution_deg=1.5, canny_low_thresh=50, canny_high_thresh=150) -> list[visiong.Line]
使用 Canny 边缘检测和霍夫线变换在图像中查找直线。
roi(tuple, optional): 感兴趣区域(x, y, w, h)。threshold(int, optional): 霍夫变换累加器的投票阈值。值越高,检测标准越严格。rho_resolution_px(float, optional): 极径 `rho` 的分辨率(像素)。theta_resolution_deg(float, optional): 极角 `theta` 的分辨率(度)。canny_low_thresh` / `canny_high_thresh(int, optional): Canny 边缘检测的阈值。- 返回:
visiong.Line对象列表。
img.find_circles(roi=(0,0,0,0), x_stride=1, y_stride=1, threshold=35, r_min=10, r_max=0, r_step=2, canny_low_thresh=50, canny_high_thresh=100) -> list[visiong.Circle]
使用霍夫圆变换在图像中查找圆形。
roi(tuple, optional): 感兴趣区域(x, y, w, h)。threshold(int, optional): 霍夫圆变换累加器的阈值。r_min` / `r_max(int, optional): 要搜索的圆的最小和最大半径。r_max=0表示自动确定。r_step(int, optional): 半径搜索步长。canny_low_thresh` / `canny_high_thresh(int, optional): Canny 边缘检测的阈值。- 返回:
visiong.Circle对象列表。
img.find_qrcodes() -> list[visiong.QRCode]
在图像中查找并解码二维码。 该函数会自动将图像转换为灰度图进行处理。
- 返回:
visiong.QRCode对象列表。每个对象包含corners(四个角的坐标) 和payload(解码后的内容,为bytes类型)。
img.find_polygons(roi=(0,0,0,0), threshold_val=128, min_area=100, max_area=100000, min_sides=3, max_sides=10) -> list[Polygon]
在二值化后的图像中查找并返回多边形轮廓。
roi(tuple, optional): 感兴趣区域(x, y, w, h)。threshold_val(int, optional): 用于将图像二值化的亮度阈值。min_area` / `max_area(int, optional): 用于过滤多边形的面积范围。min_sides` / `max_sides(int, optional): 用于过滤多边形的边数范围。- 返回:
Polygon对象列表,每个多边形是一个顶点元组的列表。
4. `visiong.Camera` 类
此类提供了对摄像头硬件的完全控制,从初始化、捕获到精细的图像质量(ISP)调整。
初始化与释放
visiong.Camera(target_width, target_height, format="yuv420", hdr=False)
初始化并准备摄像头硬件。 这是一个资源密集型操作,它会初始化系统、运行 ISP 并配置视频输入通道。可以不带参数构造,后续再调用 init() 方法。
target_width(int): 期望的最终图像宽度。target_height(int): 期望的最终图像高度。format(str, optional): 期望的输出格式,默认为"yuv420"。其他可用如"bgr","rgb","gray"。hdr(bool, optional): 是否启用 HDR 模式,默认为False。
cam.release() -> None
释放摄像头硬件资源。 程序结束前必须调用此方法以避免资源泄露。
最佳实践: 强烈建议将摄像头操作放在try...finally块中,确保release()总能被正确调用,即使在程序出错时。
import visiong
cam = visiong.Camera()
try:
cam.init(640, 360)
# ... 你的代码 ...
img = cam.snapshot()
finally:
cam.release()
print("Camera released.")
核心方法与属性
cam.snapshot() -> visiong.ImageBuffer
从摄像头捕获一帧图像。 这是一个阻塞调用,返回一个高效的 Zero-copy 硬件内存缓冲区的封装。这意味着数据没有发生不必要的拷贝,性能极高。当返回的 `ImageBuffer` Python 对象被垃圾回收时,底层硬件缓冲会自动释放。
cam.skip(num_frames: int = 10) -> None
读取并丢弃指定数量的帧。强烈建议在初始化摄像头后调用此方法,以便 ISP 的自动曝光、自动白平衡等算法在开始处理实际帧之前有时间稳定下来。
cam.is_initialized() -> bool
检查摄像头是否已成功初始化。
cam.target_width / cam.target_height -> int
获取用户在初始化时设定的目标宽度和高度。
cam.actual_width / cam.actual_height -> int
获取硬件实际捕获的图像宽度和高度。由于硬件对齐等原因,这个值可能与 `target_width` 不同。
ISP 控制方法
这些方法允许您动态调整摄像头的成像效果。所有 set_* 方法在设置失败时会抛出异常。对应的 get_* 方法可以获取当前值。
set_saturation(value: int) / get_saturation() -> int: 设置/获取图像饱和度。范围: [0, 255]。set_contrast(value: int) / get_contrast() -> int: 设置/获取图像对比度。范围: [0, 255]。set_brightness(value: int) / get_brightness() -> int: 设置/获取图像亮度。范围: [0, 255]。set_sharpness(value: int) / get_sharpness() -> int: 设置/获取图像锐度。范围: [0, 100]。set_hue(value: int) / get_hue() -> int: 设置/获取图像色调。范围: [0, 255]。set_exposure_mode(mode: str) / get_exposure_mode() -> str: 设置/获取曝光模式 ("auto"或"manual")。set_exposure_time(seconds: float) / get_exposure_time() -> float: 在手动模式下,设置/获取曝光时间(单位:秒)。set_exposure_gain(gain: int) / get_exposure_gain() -> int: 在手动模式下,设置/获取曝光增益。set_white_balance_mode(mode: str) / get_white_balance_mode() -> str: 设置/获取白平衡模式 ("auto"或"manual")。set_white_balance_temperature(temp: int) / get_white_balance_temperature() -> int: 在手动模式下,设置/获取白平衡色温。set_flip(flip: bool, mirror: bool): 设置图像的垂直翻转 (flip) 和水平镜像 (mirror)。set_spatial_denoise_level(level: int): 设置空间 (2D) 降噪级别。范围: [0, 100]。set_temporal_denoise_level(level: int): 设置时间 (3D) 降噪级别。范围: [0, 100]。set_frame_rate(fps: int): 设置摄像头帧率。范围: [10, 60] (或 0 表示自动)。set_power_line_frequency(mode: str): 设置电源频率抗闪烁模式 ("50hz","60hz", 或"off")。
对焦控制方法:
set_focus_mode(mode: str): 设置自动对焦模式。支持:'continuous': 连续自动对焦。'manual': 手动对焦。
set_manual_focus(position: int): 在'manual'模式下,将镜头马达移动到指定位置。lock_focus(): 锁定自动对焦在当前位置,防止进一步调整。unlock_focus(): 解锁自动对焦,使其恢复到配置的模式 (例如'continuous')。trigger_focus(): 执行一次单次的自动对焦搜索。get_focus_position() -> int: 获取当前镜头马达的位置代码 (如果失败则返回 -1)。
5. `visiong.NPU` 类
此类封装了模型的加载和推理流程,极大简化了在 Rockchip NPU 上运行 AI 应用的复杂度。
初始化
visiong.NPU(model_type, model_path, label_path="", box=0.25, nms=0.45)
加载 NPU 模型并初始化推理环境。 此构造函数会读取 .rknn 文件,在 NPU 上分配内存,并准备好后处理所需的参数。
model_type(str): 模型类型,支持"yolov5","retinaface","facenet","yolo11","lprnet"(不区分大小写)。model_path(str):.rknn模型文件的路径。label_path(str, optional): 标签文件的路径 (YOLOv5/YOLO11必需,每行一个类别名)。box(float, optional): 目标框置信度阈值 (用于YOLOv5/YOLO11),默认为0.25。nms(float, optional): 非极大值抑制 (NMS) 的 IoU 阈值 (用于YOLOv5/YOLO11),默认为0.45。
提示: NPU 对象初始化也应使用 try...finally 结构确保资源被正确释放(尽管析构函数会自动处理)。
核心方法
npu.infer(img, roi=(0,0,0,0), model_format="rgb") -> list[visiong.Detection]
对输入的 ImageBuffer 执行一次目标检测推理。
此方法内部集成了一个高效的预处理、推理和后处理流水线。它会自动处理 RGA 加速的颜色格式转换、ROI裁剪和 Letterbox 变换,以匹配模型输入要求。推理结束后,它还会将检测框坐标反算回原始图像坐标系,极大方便了后续应用。仅适用于 yolov5, yolo11 和 retinaface 模型。
img(ImageBuffer): 待推理的输入图像。roi(tuple, optional): 指定在图像的哪个区域进行推理,格式为(x, y, w, h)。默认为全图。model_format(str, optional): 告知NPU你的.rknn模型需要哪种颜色顺序的输入,"rgb"或"bgr"。默认为"rgb"。- 返回: 一个
visiong.Detection对象的列表。
npu.get_face_feature(face_image: ImageBuffer) -> list[float]
从一张已裁剪对齐的人脸图像中提取特征向量。 此方法专为 facenet 模型设计。
face_image(ImageBuffer): 输入的人脸图像。通常是由 RetinaFace 检测到的人脸框裁剪并校正后的结果。- 返回: 一个包含128个浮点数的列表,代表该人脸的特征向量。
npu.recognize_plate(plate_image: ImageBuffer) -> str
从一张已裁剪的车牌图像中识别车牌号码。 此方法专为 lprnet 模型设计。
plate_image(ImageBuffer): 输入的已裁剪车牌图像。- 返回: 识别出的车牌号码字符串。
visiong.NPU.get_feature_distance(feature1: list[float], feature2: list[float]) -> float
计算两个人脸特征向量之间的欧氏距离。 这是一个静态方法,可以直接通过类名调用。距离越小,表示两个人脸越相似。
feature1(list[float]): 第一个人脸的特征向量。feature2(list[float]): 第二个人脸的特征向量。- 返回: 两个向量间的距离值。通常,低于
1.0或0.9的值可被认为是同一个人。
visiong.Detection 对象
npu.infer 方法的返回列表中的元素,包含以下只读属性:
det.box(tuple):(x, y, w, h)格式的检测框坐标,已映射回原始图像尺寸。det.score(float): 检测结果的置信度 (0.0 - 1.0)。det.class_id(int): 检测到的类别的整数 ID。det.label(str): 检测到的类别的字符串标签。
6. 显示类
visiong.DisplayFB (屏幕显示)
此类用于将图像通过 Framebuffer 显示到屏幕上。它在后台启动一个专门的显示线程,通过非阻塞的 display(img) 方法提交任务,确保主线程不会因为等待屏幕刷新而卡顿。
visiong.DisplayFB(mode="high", screen_format="rgb565")
mode(str, optional): 显示模式,"high"(高刷新率) 或"low"(低刷新率),默认为"high"。screen_format(str, optional): 屏幕的像素格式,通常是"rgb565"或"bgr565"。
display.display(img: ImageBuffer, roi: tuple) -> bool
请求显示一个 ImageBuffer。可以只显示图像的某个ROI区域。这是一个非阻塞调用。
visiong.DisplayUDP (网络流)
此类用于将图像进行硬件 JPEG 编码后,通过 UDP 网络流发送出去。
visiong.DisplayUDP(udp_ip="172.32.0.100", udp_port=8000, jpeg_quality=75)
udp_ip(str): 目标接收端的 IP 地址。udp_port(int): 目标接收端的端口。jpeg_quality(int): JPEG 压缩质量 (1-100)。
display.display(img: ImageBuffer)
对图像进行JPEG编码并通过UDP发送。
visiong.DisplayHTTP (网络流)
此类用于启动一个 MJPEG-over-HTTP 流媒体服务器。它在初始化时自动启动,允许您通过 Web 浏览器(如 http://<board_ip>:8080)查看视频流。
visiong.DisplayHTTP(port=8080, quality=75)
port(int): 用于 HTTP 连接的 TCP 端口。quality(int): JPEG 压缩质量 (1-100)。
display.display(img: ImageBuffer)
将图像编码为JPEG,并将其推送给所有连接的HTTP客户端。
display.stop()
停止HTTP服务器并断开所有客户端。这会在对象被垃圾回收时自动调用。
display.is_running() -> bool
检查服务器是否正在运行。
7. IVE (Intelligent Video Engine)
IVE 简介
此类提供了对 Rockchip IVE (智能视频引擎) 硬件加速功能的底层访问。IVE 专为传统的、非-ML (非机器学习) 的计算机视觉任务而设计,如形态学操作、滤波、边缘检测、运动跟踪等。
visiong.IVE 是一个单例类,您可以通过调用 ive = visiong.IVE() 来获取全局实例。
辅助枚举与类
IVE 方法经常使用特定的枚举来控制其行为。这些枚举作为 visiong 模块的属性暴露出来。
visiong.ImageTypeIVE: 图像格式 (U8C1,S16C1,U16C1,YUV420SP等)。visiong.SobelOutCtrl: Sobel 输出控制 (HOR,VER,BOTH)。visiong.OrdStatFilterMode: 统计滤波器模式 (MEDIAN,MAX,MIN)。visiong.SubMode: 减法模式 (ABS,SHIFT)。visiong.LogicOp: 逻辑运算 (AND,OR,XOR)。visiong.ThreshMode: 阈值模式 (BINARY,TRUNC,TO_MINVAL)。visiong.CscMode: 颜色空间转换模式 (YUV2RGB_BT601_LIMITED,RGB2YUV_BT601_LIMITED等)。visiong.Cast16to8Mode: 16位转8位模式 (S16_TO_S8,S16_TO_U8_ABS等)。visiong.DmaMode: DMA 模式 (DIRECT_COPY,INTERVAL_COPY等)。visiong.SadMode: SAD 模式 (MB_4X4,MB_8X8,MB_16X16)。
visiong.IVEModel(width, height, model_size=0)
创建一个用于有状态 IVE 算法(如GMM)的持久内存块。
visiong.MotionVector
一个只读数据对象,由 lk_optical_flow 返回,包含 .status, .mv_x, 和 .mv_y 属性。
IVE 方法
所有 IVE 方法都返回一个新的 ImageBuffer 对象,除非另有说明。
ive.filter(src, mask) -> visiong.ImageBuffer
执行一个 5x5 滤波器操作。src 必须是 GRAY8。mask 必须是一个包含25个整数(int8)的列表。
ive.sobel(src, out_ctrl, out_format) -> tuple[ImageBuffer, ImageBuffer]
执行 Sobel 边缘检测。src 必须是 GRAY8。返回一个包含 (horizontal_edges, vertical_edges) 的元组。
ive.canny(src, high_thresh, low_thresh) -> visiong.ImageBuffer
执行 Canny 边缘检测。src 必须是 GRAY8。
ive.mag_and_ang(src, threshold=0, return_magnitude=True) -> visiong.ImageBuffer
计算梯度幅度和角度。返回幅度 (S16C1) 或角度 (U8C1) 图像。
ive.dilate(src, kernel_size=3) -> visiong.ImageBuffer
执行形态学膨胀。src 必须是 GRAY8。kernel_size 可以是 3 或 5。
ive.erode(src, kernel_size=3) -> visiong.ImageBuffer
执行形态学腐蚀。src 必须是 GRAY8。kernel_size 可以是 3 或 5。
ive.ordered_stat_filter(src, mode) -> visiong.ImageBuffer
执行有序统计滤波器 (中值、最大、最小)。src 必须是 GRAY8。mode 使用 visiong.OrdStatFilterMode 枚举。
ive.add(src1, src2) -> visiong.ImageBuffer
执行两个图像的像素级相加。
ive.sub(src1, src2, mode) -> visiong.ImageBuffer
执行两个图像的像素级相减。mode 使用 visiong.SubMode 枚举。
ive.logic_op(src1, src2, op) -> visiong.ImageBuffer
执行像素级逻辑运算 (AND, OR, XOR)。op 使用 visiong.LogicOp 枚举。
ive.threshold(src, low_thresh, high_thresh=255, mode) -> visiong.ImageBuffer
对 GRAY8 图像执行阈值处理。mode 使用 visiong.ThreshMode 枚举。
ive.cast_16bit_to_8bit(src, mode) -> visiong.ImageBuffer
将 16 位图像 (S16C1/U16C1) 转换为 8 位图像。mode 使用 visiong.Cast16to8Mode 枚举。
ive.hist(src) -> list[int]
计算 GRAY8 图像的直方图。返回一个256个整数的列表。
ive.equalize_hist(src) -> visiong.ImageBuffer
对 GRAY8 图像执行直方图均衡化。
ive.integral(src, mode) -> visiong.ImageBuffer
计算积分图。输出为 64 位图像 (U64C1)。
ive.ccl(src, min_area=100) -> list[visiong.Blob]
执行连通分量标记 (Blob检测)。在二值化图像上操作,返回 Blob 对象列表。
ive.ncc(src1, src2) -> float
计算两个图像的归一化互相关 (NCC),返回一个浮点值。
ive.csc(src, mode) -> visiong.ImageBuffer
执行颜色空间转换 (例如 YUV 到 RGB)。mode 使用 visiong.CscMode 枚举。
ive.dma(src, mode) -> visiong.ImageBuffer
执行直接内存访问操作 (例如,直接复制)。
ive.cast_8bit_to_8bit(src, bias, numerator, denominator) -> visiong.ImageBuffer
使用公式 dst = (src * numerator / denominator) + bias 缩放 8 位图像。
ive.map(src, lut) -> visiong.ImageBuffer
使用查找表 (LUT) 映射图像像素值。lut 必须是一个包含256个整数的列表。
ive.gmm(src, model, first_frame=False) -> tuple[ImageBuffer, ImageBuffer]
执行高斯混合模型 (GMM) 背景减除。返回 (foreground_img, background_img) 元组。
ive.gmm2(src, factor, model, first_frame=False) -> tuple[ImageBuffer, ImageBuffer]
执行带有因子图像的高级 GMM 背景减除。返回 (foreground_img, background_img) 元组。
ive.lbp(src, abs_mode=False, threshold=0) -> visiong.ImageBuffer
计算图像的局部二值模式 (LBP)。
ive.norm_grad(src) -> tuple[ImageBuffer, ImageBuffer]
计算归一化梯度。返回 (horizontal_grad, vertical_grad) 元组。
ive.lk_optical_flow(prev_img, next_img, points) -> list[visiong.MotionVector]
对一个点列表执行 Lucas-Kanade 光流跟踪。返回 MotionVector 对象列表。
ive.st_corner(src, max_corners=200, min_dist=10, quality_level=25) -> list[tuple]
执行 Shi-Tomasi 角点检测。返回一个 (x, y) 角点元组的列表。
ive.match_bg_model(current_img, bg_model, frame_num) -> visiong.ImageBuffer
将当前图像与背景模型进行匹配。返回一个前景标志图像。
ive.update_bg_model(current_img, fg_flag, bg_model, frame_num) -> visiong.ImageBuffer
更新背景模型。返回新的背景图像。
ive.sad(src1, src2, mode, threshold, min_val=0, max_val=255) -> tuple[ImageBuffer, ImageBuffer]
计算绝对差值和 (SAD)。mode 使用 visiong.SadMode 枚举。返回 (sad_image, threshold_image) 元组。
ive.create_pyramid(src, levels) -> list[visiong.ImageBuffer]
创建一个图像金字塔。返回一个 ImageBuffer 对象的列表,从最大到最小。
8. 通用硬件接口控制
除了 visiong 模块提供的强大视觉功能外,我们经常需要与 GPIO、PWM、串口等底层硬件直接交互。以下章节将介绍如何使用 visiong 内置的 Touch 类,以及 periphery、pyserial、smbus2 等常用 Python 库来实现这些硬件的控制。
8.1 Touch (触摸屏)
visiong 模块内置了对 I2C 触摸屏的控制。它通过一个统一的 Touch 类提供接口,自动处理坐标转换和多点触控。
visiong.Touch(chip_model="FT6336U", i2c_bus="/dev/i2c-3", original_width=None, original_height=None, rotation_degrees=None)
初始化触摸控制器。构造函数会自动打开I2C总线并验证芯片ID。如果提供了屏幕的原始几何参数,它将自动配置坐标变换。
chip_model(str): 触摸芯片型号,目前支持"FT6336U"。i2c_bus(str): I2C 总线设备文件路径。original_width/height(int, optional): 屏幕物理分辨率的宽度和高度。rotation_degrees(int, optional): 屏幕的旋转角度 (0, 90, 180, 270),用于正确映射触摸坐标。
touch.read() -> list[TouchPoint]
读取所有当前触摸点的位置,返回一个 TouchPoint 对象列表。每个 TouchPoint 对象有 .x 和 .y 属性。
import visiong
import time
try:
# 初始化触摸屏,假设屏幕是240x320,旋转270度后变为320x240
touch = visiong.Touch(
original_width=240,
original_height=320,
rotation_degrees=270
)
while True:
points = touch.read()
if points:
for i, p in enumerate(points):
print(f"Point {i+1}: ({p.x}, {p.y})")
time.sleep(0.1)
except KeyboardInterrupt:
print("Exiting.")
finally:
if 'touch' in locals():
touch.release()
8.2 控制GPIO
本节演示如何使用 periphery 库控制 GPIO 引脚的输出和输入状态。代码将一个引脚设置为输出并循环切换电平,同时读取另一个输入引脚的状态。
完整代码
from periphery import GPIO
import time
# 根据你的硬件原理图修改引脚号
WRITE_PIN = 55
READ_PIN = 54
# 打开 GPIO
try:
write_gpio = GPIO(WRITE_PIN, "out")
read_gpio = GPIO(READ_PIN, "in")
while True:
# 设置高电平并读取
write_gpio.write(True)
pin_state = read_gpio.read()
print(f"Write Pin HIGH -> Read Pin State: {pin_state}")
time.sleep(1)
# 设置低电平并读取
write_gpio.write(False)
pin_state = read_gpio.read()
print(f"Write Pin LOW -> Read Pin State: {pin_state}")
time.sleep(1)
except KeyboardInterrupt:
print("\nExiting program.")
except (IOError, ValueError) as e:
print(f"GPIO Error: {e}")
finally:
# 确保在退出时关闭 GPIO
if 'write_gpio' in locals():
write_gpio.write(False) # 安全起见,先设为低电平
write_gpio.close()
if 'read_gpio' in locals():
read_gpio.close()
8.3 控制PWM
此示例展示了如何使用 periphery 库的 PWM 类来驱动一个 PWM 输出,通过周期性地改变占空比,实现一个平滑的呼吸灯效果。
示例程序
from periphery import PWM
import time
# PWM(chip, channel) -> 打开 PWM 10, 通道 0, 根据硬件修改
try:
pwm = PWM(10, 0)
pwm.frequency = 1000 # 1kHz
pwm.duty_cycle = 0.0
pwm.enable()
direction = 1
print("PWM breathing light effect. Press Ctrl+C to stop.")
while True:
new_duty_cycle = pwm.duty_cycle + 0.01 * direction
if new_duty_cycle > 1.0:
new_duty_cycle = 1.0
direction = -1
elif new_duty_cycle < 0.0:
new_duty_cycle = 0.0
direction = 1
pwm.duty_cycle = new_duty_cycle
time.sleep(0.02)
except KeyboardInterrupt:
print("\nStopping PWM.")
except Exception as e:
print(f"An error occurred: {e}")
finally:
if 'pwm' in locals():
pwm.duty_cycle = 0.0
pwm.disable()
pwm.close()
8.4 串口通信
本节分别演示如何使用主流的 pyserial 库和 periphery 库进行串口通信,两者都可以完成任务,可根据个人偏好或项目依赖选择。
使用 pyserial (推荐)
import serial
try:
# 使用 'with' 语句可以自动管理串口的打开和关闭
with serial.Serial(port="/dev/ttyS3", baudrate=115200, timeout=1) as uart:
message = b"Hello from pyserial!\n"
uart.write(message)
print(f"Sent: {message.decode().strip()}")
response = uart.readline() # 读取一行数据,直到换行符或超时
if response:
print(f"Received: {response.decode().strip()}")
else:
print("No response received within timeout.")
except serial.SerialException as e:
print(f"Serial port error: {e}")
使用 python-periphery
from periphery import Serial
import time
serial_port = None
try:
serial_port = Serial("/dev/ttyS3", 115200)
message = b"Hello from periphery!\n"
serial_port.write(message)
print(f"Sent: {message.decode().strip()}")
# 读取最多 128 字节,超时 1 秒
response = serial_port.read(128, 1.0)
if response:
print(f"Received: {response.decode('utf-8', errors='ignore').strip()}")
else:
print("No response received within timeout.")
except Exception as e:
print(f"An error occurred: {e}")
finally:
if serial_port is not None and serial_port.is_open:
serial_port.close()
8.5 读取ADC
本示例通过直接读取 sysfs 文件系统中的 IIO (Industrial I/O) 设备节点来获取 ADC 通道的电压值。这是在 Linux 系统上与许多硬件传感器交互的通用方法。
完整代码
import time
import os
ADC_DEVICE_PATH = "/sys/bus/iio/devices/iio:device0"
def read_sysfs_value(file_path):
"""安全地从 sysfs 文件读取一个值。"""
with open(file_path, "r") as f:
return f.read().strip()
def main():
if not os.path.isdir(ADC_DEVICE_PATH):
print(f"Error: IIO device not found at {ADC_DEVICE_PATH}")
return
print("Reading ADC values. Press Ctrl+C to quit.")
try:
# 读取一次 scale 值,它通常是固定的
scale_path = os.path.join(ADC_DEVICE_PATH, "in_voltage_scale")
scale = float(read_sysfs_value(scale_path))
raw0_path = os.path.join(ADC_DEVICE_PATH, "in_voltage0_raw")
raw1_path = os.path.join(ADC_DEVICE_PATH, "in_voltage1_raw")
while True:
raw0 = float(read_sysfs_value(raw0_path))
raw1 = float(read_sysfs_value(raw1_path))
# 电压 (单位:伏特) = 原始值 * scale / 1000.0
voltage0 = (raw0 * scale) / 1000.0
voltage1 = (raw1 * scale) / 1000.0
print(f"Channel 0: {voltage0:.3f} V | Channel 1: {voltage1:.3f} V", end='\r')
time.sleep(1)
except (FileNotFoundError, IOError) as e:
print(f"\nError reading ADC value: {e}")
except KeyboardInterrupt:
print("\nExiting.")
if __name__ == "__main__":
main()
8.6 I2C通讯
此代码使用 smbus2 库(smbus 的现代替代品)来扫描指定的 I2C 总线,并打印出所有响应的从设备地址。这是在连接新的 I2C 设备后进行调试和验证的常用工具。
完整代码
import smbus2 as smbus # 推荐使用 smbus2
def scan_i2c_bus(bus_number):
"""扫描指定的I2C总线以查找连接的设备。"""
print(f"Scanning I2C bus {bus_number}...")
found_devices = []
try:
bus = smbus.SMBus(bus_number)
# 遍历所有可能的7位地址 (0x03 到 0x77)
for address in range(0x03, 0x78):
try:
# 尝试向地址写入一个字节,如果设备存在则不会产生IOError
bus.write_quick(address)
found_devices.append(address)
except OSError:
pass # 这个地址没有设备响应
bus.close()
return found_devices
except FileNotFoundError:
print(f"Error: I2C bus {bus_number} not found. Check /dev/i2c-*")
return []
if __name__ == "__main__":
bus_to_scan = 3 # 根据你的硬件连接修改总线号
devices = scan_i2c_bus(bus_to_scan)
if devices:
print(f"Found {len(devices)} device(s) at addresses:")
hex_addresses = [f"0x{addr:02X}" for addr in devices]
print(" ".join(hex_addresses))
else:
print("No I2C devices found on this bus.")
8.7 SPI通讯
本节演示了如何使用 spidev 库进行 SPI 总线的数据回环测试。要成功运行此测试,需要将开发板上的 SPI MOSI (Master Out, Slave In) 引脚与 MISO (Master In, Slave Out) 引脚物理连接。
完整代码
import spidev
def spi_loopback_test(bus, device):
"""在指定的SPI总线和设备上执行回环测试。"""
spi = spidev.SpiDev()
try:
# 打开SPI设备
spi.open(bus, device)
# 设置SPI参数
spi.max_speed_hz = 1000000 # 1 MHz
spi.mode = 0b00 # 模式0 (CPOL=0, CPHA=0)
# 准备要发送的数据
tx_data = list(range(10))
print(f"Sending: {tx_data}")
# 执行全双工传输
rx_data = spi.xfer2(tx_data)
print(f"Received: {rx_data}")
# 验证数据
if tx_data == rx_data:
print("✅ Loopback test PASSED: Data matches.")
else:
print("❌ Loopback test FAILED: Data mismatch.")
except FileNotFoundError:
print(f"Error: SPI device (bus {bus}, device {device}) not found. Check /dev/spidev*")
except Exception as e:
print(f"An error occurred: {e}")
finally:
spi.close()
if __name__ == "__main__":
# 运行测试前,请确保将 bus 0, device 0 的 MOSI 和 MISO 引脚短接
print("--- SPI Loopback Test ---")
print("Ensure MOSI and MISO pins are connected for this test.")
spi_loopback_test(bus=0, device=0)