news 2026/9/10 22:35:54

Gradio ImageEditor 核心编辑器架构解析:基于 PIXI.js 与命令模式的画布引擎设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gradio ImageEditor 核心编辑器架构解析:基于 PIXI.js 与命令模式的画布引擎设计

Gradio ImageEditor 核心编辑器架构解析:基于 PIXI.js 与命令模式的画布引擎设计

【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio

本篇技术指南深入剖析 Gradio 图像编辑器(ImageEditor)的核心引擎(Core Editor)——一个基于 PIXI.js 构建、以命令模式驱动撤销/重做、以 Svelte Store 管理响应式状态的画布渲染架构。文章围绕 核心编辑器文档 展开,并结合 editor.ts、commands.ts、layers.ts 等源码,系统讲解 ImageEditor、CommandManager、LayerManager、EditorState 与 Tool 接口的协作关系,以及渲染管线、事件处理、性能优化与自定义 API,帮助读者掌握 Gradio 图像编辑器前端的底层实现与二次开发方法。

一、概览:核心编辑器在 Gradio 中的定位

核心编辑器(Core Editor)是 Gradio ImageEditor 组件的中枢,负责管理画布(canvas)、工具(tools)、图层(layers)与用户交互。它提供了高度灵活的架构来集成各类工具并维护编辑器状态。在 Gradio 的整体结构中,它位于前端 Svelte 组件层,由 ImageEditor.svelte(主集成组件)、Toolbar.svelte(工具类型定义与工具选择)和 editor.ts(编辑器主实现)三者协同完成。

该编辑器与 Gradio 的 Python 后端 ImageEditor 组件(gradio/components/image_editor)配合使用,前端负责交互与画布渲染,后端负责数据模型与图像处理,二者通过ImageBlobs(见 types.ts)传递最终图像数据。本文聚焦前端核心引擎的内部工作原理。

二、架构总览:五个核心类如何协同

编辑器围绕五个关键类构建,它们各司其职又紧密耦合:

类/接口职责源码位置
ImageEditor主入口类,初始化并管理编辑器整体生命周期editor.ts
CommandManager通过命令模式实现撤销/重做commands.ts
LayerManager管理图层及其纹理资源layers.ts
EditorState维护编辑器状态并通知订阅者editor.ts
Tool 接口定义所有工具必须实现的契约editor.ts

它们之间的关系可以概括为:ImageEditorinit()阶段创建 PIXI.js 应用、容器与LayerManager,将CommandManager实例暴露给所有工具;工具通过ImageEditorContext(上下文对象)访问command_managerlayer_manager以及各种状态 Store;EditorState作为内部状态订阅器,桥接 Svelte 的 spring store 与外部订阅者。

2.1 设计模式要点

  • 命令模式(Command Pattern):所有可撤销操作(添加图层、删除图层、重排序、绘制、添加图片)都被封装为 Command 对象,由 CommandManager 统一调度。
  • 上下文注入(Context Injection):编辑器通过ImageEditorContext接口向工具注入全部依赖,工具不直接持有编辑器引用,降低耦合度。
  • 发布-订阅(Pub/Sub)EditorState维护订阅者集合,状态变化时广播;编辑器自身也维护change/input事件回调。

三、ImageEditor 主类:入口与生命周期

ImageEditor类(editor.ts)是编辑器的主入口,提供以下核心能力:

  1. 初始化:创建 PIXI.jsApplication、各级容器与初始状态;
  2. 工具管理:注册并管理工具实例(核心工具imagezoom,以及外部传入的 brush、crop、resize 等);
  3. 图层管理:通过LayerManager创建、删除、重排序图层;
  4. 命令执行:通过CommandManager执行命令并管理撤销/重做;
  5. 状态管理:维护并更新编辑器状态;
  6. 渲染:处理渲染循环与更新。

3.1 构造选项(ImageEditorOptions)

构造器接收的选项(editor.ts)决定了编辑器的行为:

选项类型默认值说明
target_elementHTMLElement必填编辑器挂载的 DOM 元素
width/heightnumber必填画布初始尺寸(像素)
tools("image" \| "zoom" \| Tool)[]必填工具列表,字符串表示使用内置核心工具
fixed_canvasbooleanfalse是否固定画布大小(开启后图像在画布内自适应摆放)
darkbooleanfalse暗色模式标志
border_regionnumber0画布周围留白区域(用于外扩绘制 outpainting)
layer_optionsLayerOptions见下图层初始化选项
pad_bottomnumber0底部内边距
theme_mode"dark" \| "light""dark"主题模式,决定背景色

layer_options的默认值为{ allow_additional_layers: true, layers: ["Layer 1"], disabled: false }(editor.ts),对应 types.ts 中的LayerOptions接口。

内置核心工具通过core_tool_map(editor.ts)映射:字符串"image"实例化ImageTool(见 image/image.ts),"zoom"实例化ZoomTool(见 zoom/zoom.ts)。外部自定义工具直接以对象形式传入,以其name属性作为注册键(editor.ts)。

3.2 初始化流程(init 方法)

init()(editor.ts)是核心启动序列:

  1. 通过ResizeObserver监听容器尺寸变化,自动调用resize_canvas
  2. 创建 PIXI.jsApplication,关键配置包括:
    • resolution: window.devicePixelRatio+autoDensity: true——适配高 DPI 屏幕;
    • antialias: true——抗锯齿;
    • powerPreference: "high-performance"——优先使用高性能 GPU;
    • 背景色根据theme_mode#27272a(暗色)或#ffffff(亮色);
  3. 调用setup_containers()建立渲染层级;
  4. 创建背景图层与初始化图层(create_background_layer+init_layers);
  5. 依次调用每个工具的setup(context, current_tool, current_subtool)
  6. 将 canvas 挂载到target_element
  7. 订阅dimensions/scale/position三个 spring store,并注册app.ticker渲染循环;
  8. 在 canvas 上监听WAKE_EVENTSpointerdownpointermovepointeruppointerenterwheel),用于唤醒渲染循环;
  9. 最终调用ready_resolve(),对外暴露ready: Promise<void>,供调用方等待初始化完成。

3.3 上下文对象(ImageEditorContext)

contextgetter(editor.ts)将编辑器内部能力封装成只读上下文,供工具使用:

export interface ImageEditorContext { app: Application; ui_container: Container; image_container: Container; background_image?: Sprite; command_manager: CommandManager; layer_manager: LayerManager; dimensions: Readable<{ width: number; height: number }>; scale: Readable<number>; position: Readable<{ x: number; y: number }>; set_image_properties: (properties: {...}) => Promise<void>; execute_command: (command: Command) => Promise<void> | void; resize_canvas: (width: number, height: number) => void; reset: () => void; set_background_image: (image: Sprite) => void; request_render: () => void; pad_bottom: number; }

注意dimensionsscaleposition在上下文中仅暴露subscribe能力(Readable),写入只能通过set_image_properties完成——这保证了状态修改路径的统一与可追踪。

四、CommandManager 与命令模式:撤销/重做的实现

4.1 Command 接口契约

commands.ts 定义了命令接口,所有可撤销操作都实现它:

成员说明
name命令名称,用于历史查询(如"AddLayer""RemoveLayer""ReorderLayer"
start/continue/stop(可选)多步命令的分阶段回调(如拖拽绘制过程中调用)
execute(context?)执行命令,必须能够在撤销后再次执行以重建效果
undo()撤销命令,必须能够还原execute所做的工作

关键约束(源码注释明确强调):execute函数必须可重放——即使start/continue/stop不再被调用,它也要能重建命令效果;undo必须能完整撤销execute的工作。这是命令可撤销性的根基。

4.2 CommandNode:双向链表历史

CommandManager采用双向链表CommandNode)而非数组来维护历史(commands.ts):

export class CommandNode { command: Command | null; next: CommandNode | null; previous: CommandNode | null; push(command: Command): void { this.next = null; const node = new CommandNode(command); node.previous = this; this.next = node; } }

链表结构的好处:

  • O(1) 撤销/重做:撤销只需回退到previous节点,重做只需前进到next节点;
  • 分支截断:执行新命令时pushnext置空,自动丢弃旧的 redo 分支;
  • 完整历史可重放replay()可从链表头遍历重放全部历史,用于状态恢复。

4.3 CommandManager 核心 API

方法行为
execute(command, context)先执行command.execute(context),再 push 到历史链尾
undo()若有previous节点,调用当前命令的undo()并回退指针
redo(context)若有next节点,前进指针并调用execute(context)
reset()重置历史为空白链表(如加载新图片时)
contains(command_name)遍历链表判断历史中是否包含指定命令
replay(full_history, context)从链表头重放全部命令(用于恢复完整编辑历史)

current_history是一个 Sveltewritablestore,每次历史变化都会更新,使得 UI 可以响应式地感知撤销/重做可用状态。在 ImageEditor.svelte 中,can_undo状态正是基于此机制驱动撤销按钮的可用性。

4.4 实际命令示例

layers.ts 中实现了三个图层命令:

  • AddLayerCommandname: "AddLayer"):execute调用layer_manager.create_layer创建图层;undo调用delete_layer删除并恢复此前的活动图层。
  • RemoveLayerCommandname: "RemoveLayer"):构造时预先复制图层纹理captureTextureData创建RenderTexture副本);undo时重建图层并恢复纹理内容——这保证了删除图层可被完整还原。
  • ReorderLayerCommandname: "ReorderLayer"):构造时记录original_orderundo根据当前顺序与原始顺序的差异决定移动方向。

图像工具同样遵循该模式:ImageTool.add_image 将添加图片封装为AddImageCommand后通过context.execute_command提交。

五、LayerManager 与图层管理

LayerManager(layers.ts)是图层系统的核心,每个图层对应一个 PIXIContainer与其RenderTexture绘制纹理。

5.1 核心能力

  1. 图层创建create_layer创建Container并为其生成独立RenderTexture,用SCALE_MODES.NEAREST保持像素清晰度;图层 ID 默认由Math.random().toString(36).substring(2, 15)生成;
  2. 图层删除delete_layer销毁绘制纹理与容器,并自动将活动图层切换为相邻图层;
  3. 图层排序update_layer_order依据数组索引设置zIndex,背景层固定为-1
  4. 活动图层set_active_layer/get_active_layer跟踪当前编辑目标;
  5. 背景图层create_background_layer特殊处理——用纯色填充(暗色0x333333,亮色0xffffff)创建最底层,供图像置底。

5.2 图层状态 Store

layer_store是一个 Sveltewritablestore(layers.ts),结构为:

{ active_layer: string; layers: { name: string; id: string; user_created: boolean; visible: boolean }[]; }

ImageEditor将该 store 暴露为自己的layers属性(editor.ts),Svelte 组件(如 Layers.svelte)订阅它来渲染图层面板。

5.3 图层选项与固定画布

  • LayerOptions(types.ts):allow_additional_layers是否允许用户新增图层、layers初始图层名列表、disabled是否禁用图层功能。
  • init_layerslayer_options.layers批量创建初始图层(ID 形如layer-0layer-1);
  • fixed_canvas模式下,图像被约束在画布内部并按比例缩放居中(create_background_layer_from_urladd_layer_from_url中的缩放逻辑,layers.ts)。

5.4 图层缩放

resize_all_layers(layers.ts)支持 9 个锚点(top-leftcenterbottom-right等)与两种模式:scale=true拉伸内容适配新尺寸,scale=false保持内容尺寸仅按锚点偏移。

5.5 导出 Blob

get_blobs()(layers.ts)返回ImageBlobs(types.ts):

export interface ImageBlobs { background: Blob | null; layers: (Blob | null)[]; composite: Blob | null; }

即背景图、各图层、以及合成后的完整图像三份 Blob,供 Svelte 层上传回 Gradio 后端。

六、EditorState 与状态管理

EditorState(editor.ts)是内部状态管理类,遵循以下原则:

  1. 状态属性:维护scale(缩放)、position(位移)与当前工具信息;
  2. 订阅机制:通过subscribe(callback)注册订阅者,返回取消订阅函数;
  3. 通知机制_notify_subscribers广播{ property, oldValue, newValue, timestamp }结构的变化事件。

值得注意的设计细节:

  • EditorState构造函数强制校验editor instanceof ImageEditor,否则抛出"EditorState must be created by ImageEditor",防止被外部直接实例化;
  • 对外暴露的state通过Object.freeze冻结,并提供 getter 返回position/scale拷贝,避免外部直接篡改内部状态;
  • 编辑器创建dimensionsscaleposition三个Svelte spring store(editor.ts),并在订阅回调中同步到EditorState——即 spring 动画驱动的值变化会自动通知EditorState的订阅者。
const spring_config = { stiffness: 0.45, damping: 0.8 };

stiffness: 0.45damping: 0.8的弹簧配置让缩放与平移带有平滑的阻尼动画效果,避免生硬的跳变。

七、Tool 接口与工具集成

7.1 工具契约

所有工具(内置与自定义)必须实现Tool接口(editor.ts):

export interface Tool { name: string; setup(context: ImageEditorContext, tool: ToolbarTool, subtool: Subtool): Promise<void>; cleanup(): void; set_tool(tool: ToolbarTool, subtool: Subtool): void; on?: (event: string, callback: () => void) => void; off?: (event: string, callback: () => void) => void; }
方法职责
setup初始化工具,注入编辑器上下文与当前工具/子工具状态
cleanup工具停用时释放资源
set_tool活动工具切换时更新工具内部状态
on/off(可选)订阅/退订编辑器事件(如"change"

7.2 工具类型体系

Toolbar.svelte 定义了 UI 层面的工具类型:

export type Tool = "image" | "draw" | "erase" | "pan"; export type Subtool = | "upload" | "paste" | "webcam" | "color" | "size" | "crop" | "remove_background" | null;
  • Tool(主工具)image(图像操作)、draw(画笔)、erase(橡皮擦)、pan(平移);
  • Subtool(子工具):主工具下的细分模式,如image工具下的upload/paste/webcam(图片来源)、crop(裁剪)、size(调整尺寸),draw工具下的color(颜色)、size(笔刷大小)。

7.3 工具集成流程

  1. 注册:初始化时,ImageEditor构造器将所有工具加入Map<string, Tool>,字符串键指向内置工具工厂(core_tool_map),对象键使用tool.name
  2. 上下文访问init()中调用tool.setup(this.context, ...)注入ImageEditorContext
  3. 生命周期管理reset_canvasreset时对全部工具依次cleanup()后重新setup()
  4. 事件处理:工具若实现了on方法,编辑器会为其注册"change"事件回调,触发notify("change")(editor.ts)。

内置核心工具对应实现包括:image/image.ts(ImageTool)、zoom/zoom.ts(ZoomTool)、brush/brush.ts(BrushTool)、crop/crop.ts(CropTool)、resize/resize.ts(ResizeTool)。

7.4 工具栏 UI 联动

工具栏(Toolbar.svelte)根据sources(图片来源:upload/webcam/clipboard)与transforms(变换:crop/resize)动态渲染按钮(types.ts):

  • 仅在sources.includes("upload")时显示上传按钮;
  • 仅在transforms.includes("crop")时显示裁剪按钮;
  • background模式下显示裁剪与尺寸调整,非背景模式下显示上传/粘贴/摄像头。

can_edit_image派生状态(sources.length > 0 || (background && transforms.length > 0))控制整条工具栏是否渲染——没有可编辑来源时不展示工具条。

八、渲染管线:容器层级与绘制循环

8.1 容器结构

编辑器基于 PIXI.js 管理以下容器(editor.ts):

容器层级(zIndex)职责
image_container默认承载所有图层及其内容,随缩放/平移变化
ui_container默认(stage 上方)画布上层的 UI 元素
outline_container-10画布外围轮廓(含模糊阴影滤镜)
overlay_container999画布边框的虚线标记等覆盖元素

stage.sortableChildren = true开启子节点按 zIndex 排序,image_containeroverlay_container均设置eventMode: "static"以接收交互事件。

8.2 渲染循环

渲染在app.ticker回调中执行(editor.ts):

  1. 图层渲染:每个图层内容绘制到各自的RenderTexture(由工具命令触发);
  2. 容器合成:图层按image_container汇总,背景层在zIndex = -1最底层;
  3. UI 覆盖ui_container中的 UI 元素叠加在图像之上;
  4. 轮廓绘制outline_graphics依据有效画布尺寸(dimensions × scale)绘制矩形轮廓,暗色模式0x333333、亮色模式0xffffff
  5. 缩放与位移image_container.scaleposition在每帧由scale_value/position_value驱动。

8.3 按需唤醒的渲染循环(性能关键)

渲染循环采用空闲停止 + 事件唤醒策略:

const RENDER_IDLE_TIMEOUT_MS = 500; private wake_render_loop(): void { this.render_idle_remaining = RENDER_IDLE_TIMEOUT_MS; if (this.app?.ticker && !this.app.ticker.started) { this.app.ticker.start(); } }

render_idle_remaining每帧递减ticker.deltaMS,归零即ticker.stop();而wake_render_loop会重置计时器并重启 ticker。配合WAKE_EVENTS(pointer 事件与 wheel),实现"有交互就渲染、无交互立即休眠",显著降低空闲时的 GPU/CPU 开销。

8.4 边框区域(border_region)

border_region > 0时,overlay_graphics在画布四周绘制虚线标记(dashLength = 5gapLength = 5,颜色0x999999,透明度 0.7),用于标识外扩绘制的可用区域,是图像外扩(outpainting)场景的视觉引导。

九、事件处理

编辑器的事件体系分为两层:

  1. 编辑器事件on(event, callback)支持"change""input"两类事件(editor.ts),内部以Map<string, (() => void)[]>存储回调;undo()redo()add_layer()move_layer()等操作后都会触发notify("change")
  2. DOM/生命周期事件
    • ResizeResizeObserver监听容器尺寸,调用resize_canvas同步 PIXI renderer 与容器中心点;
    • Tool Selectionset_tool/set_subtool遍历所有工具调用tool.set_tool
    • Command Execution:工具通过execute_command提交命令;
    • Animation:spring store 变化驱动wake_render_loop

destroy()(editor.ts)会移除全部WAKE_EVENTS监听并调用app.destroy(),避免内存泄漏。

十、与 Svelte 的集成

编辑器深度融入 Svelte 生态:

  1. Storesdimensionsscaleposition使用 Sveltespringstore,layersbackground_image_presentmin_zoom使用writablestore;
  2. Springs:弹簧动画平滑过渡用户交互引起的缩放与平移;
  3. 组件集成:ImageEditor.svelte 将编辑器实例与 Svelte 生命周期绑定,并通过导出函数(get_blobsadd_imageadd_image_from_urladd_layers_from_url)向外部暴露编辑器能力;
  4. 响应式配置layer_optionscurrent_tool变化时通过$effect自动同步到编辑器(editor.set_layer_optionseditor.set_tool)。

十一、性能考虑

编辑器综合运用以下优化手段:

  1. 纹理管理:每个图层持有独立RenderTexture,删除图层时同步destroy()纹理;resize_all_layers后延时执行Assets.cache.reset()textureGC.run()释放旧纹理(layers.ts);
  2. 图层合成:图层以 zIndex 排序合成到image_container,避免全图重绘;
  3. 事件节流:渲染循环空闲 500ms 自动停止(见"按需唤醒"一节),pointer/wheel 事件驱动唤醒;
  4. 分辨率适配resolution: window.devicePixelRatio+autoDensity: true按设备像素比渲染,兼顾高清屏清晰度与低 DPI 性能;
  5. GPU 优先powerPreference: "high-performance"antialias优化渲染质量与性能平衡。

十二、自定义 API 一览

编辑器对外暴露以下定制接口(editor.ts):

方法签名功能
set_image_properties({ width?, height?, scale?, position?, animate? })更新画布尺寸、缩放与位移;animate=false时立即生效(硬切换)
execute_command(command: Command) => Promise<void>执行命令并入撤销栈
undo/redo() => void撤销/重做最近命令
add_image({ image, resize })添加图像到画布(内部走AddImageCommand
add_image_from_url(url: string)从 URL 加载图片作为背景层
add_layers_from_url(urls: string[])从多个 URL 批量创建图层
set_tool/set_subtool(tool, subtool?)设置活动工具/子工具
set_background_image(image: Sprite)设置背景图像
set_layer_options(options: LayerOptions)动态更新图层配置
add_layer/delete_layer/move_layer/set_layer见源码图层增删改操作(均封装为命令)
modify_canvas_size(width, height, anchor, scale)按锚点调整画布尺寸
get_blobs() => Promise<ImageBlobs>导出背景/图层/合成图像 Blob
get_crop_bounds() => Promise<{...}>获取裁剪边界与裁剪后图像
reset_canvas() => Promise<void>恢复初始画布并重置历史
destroy() => void销毁编辑器与 PIXI 应用

十三、维护注意事项

修改编辑器时需遵循以下原则(源码结构可验证):

  1. 资源清理:图层、纹理、Sprite 销毁后务必调用destroy(),防止 GPU 纹理泄漏(参考RemoveLayerCommand.destroydelete_layer);
  2. 事件监听管理:DOM 监听器必须成对添加/移除(见initdestroyWAKE_EVENTS的对称处理);EditorState.subscribe返回退订函数供组件卸载时调用;
  3. 状态更新:必须通过set_image_properties等统一方法更新状态,确保 spring 动画与EditorState通知链路完整;
  4. 命令模式:凡需支持撤销的操作必须封装为 Command,且保证execute可重放、undo可完全还原;
  5. 图层管理:新增图层操作需同步维护layer_storeupdate_layer_order,否则图层面板与 zIndex 会不一致。

十四、未来改进方向

原文档 EDITOR.md 指出以下潜在增强点:

  1. 性能优化:针对大画布进一步优化渲染(如瓦片化渲染、区域失效重绘);
  2. 工具扩展:支持更多工具与工具选项(当前core_tools仅含imagezoom,其余通过外部注入);
  3. 图层特效:添加图层效果与混合模式(blending modes)支持;
  4. 选择工具:增强选择工具与选区操作;
  5. 导出选项:增加更多导出格式与选项。

这些方向均与当前架构兼容——工具扩展可直接实现Tool接口注册,图层特效可在LayerManager的纹理合成阶段扩展,导出选项则围绕get_blobs的产物格式展开。

总结

Gradio ImageEditor 的核心编辑器是一个架构清晰、职责分离的 PIXI.js 画布引擎:ImageEditor统筹生命周期,CommandManager以双向链表实现高效撤销/重做,LayerManager以"Container + RenderTexture"模型管理图层资源,EditorState配合 Svelte spring store 提供平滑的响应式状态,Tool接口则让画笔、裁剪、缩放等工具以统一契约接入。理解这套核心架构,是深入阅读 brush/brush.ts、crop/crop.ts 等工具实现,乃至二次开发自定义图像编辑工具的前提。

【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 22:30:30

Langfuse 代码库导航图:组织仓库映射与单体仓库路由指南

Langfuse 代码库导航图&#xff1a;组织仓库映射与单体仓库路由指南 【免费下载链接】langfuse &#x1faa2; Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain…

作者头像 李华
网站建设 2026/9/10 22:30:02

CVAT数据标注怎么快速上手:从部署到交付跑通一遍

CVAT数据标注怎么快速上手&#xff1a;从部署到交付跑通一遍 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise products, as we…

作者头像 李华
网站建设 2026/9/10 22:29:07

CANN/ge GetMatchedNode API文档

GetMatchedNode 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow…

作者头像 李华
网站建设 2026/9/10 22:27:37

Zephyr RTOS 上手:west 装完工具链,20 分钟点亮第一盏 LED

Zephyr RTOS 上手&#xff1a;west 装完工具链&#xff0c;20 分钟点亮第一盏 LED 【免费下载链接】zephyr Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures. 项目地址:…

作者头像 李华
网站建设 2026/9/10 22:25:35

如何用CVAT自动标注功能给图像和视频快速预标注

如何用CVAT自动标注功能给图像和视频快速预标注 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise products, as well as labeli…

作者头像 李华