
机器人嵌入式强化学习人工智能智能硬件计算机视觉音视频【免费下载链接】microduckA Tiny biped duck robot 项目地址https://gitcode.com/gh_mirrors/mi/microduck点击查看免费下载microduck 是一个微型双足机器人项目其spaces/vision-demo是一个运行在 Hugging Face Space 上的视觉演示应用机器人在某人的家庭网络里把相机画面以 H.264 流的形式推送到这个 SpaceSpace 端用 OpenCV 对每一帧解码后的画面做实时处理。本文以该 Space 的设计文档 spaces/vision-demo/README.md 为主体结合 mediad/src/stream.rs 与spaces/vision-demo/下的全部源码完整讲解这套机器人主动拨号、外向 WebSocket 直连、无 relay、无 WebRTC的帧流方案——读完你既能照文档本地跑起来也能从源码层面理解它的协议、取舍与部署细节。设计核心机器人拨号这就是全部设计这个 Space 的第一句话就点明了它的本质一台在某人家庭网络里的机器人把相机以 H.264 流的形式推送到这个 SpaceSpace 上由 OpenCV 对每一帧解码后的画面运行处理。机器人的网络上没有任何入站暴露也没有任何 relay 参与。不要直接编辑这个 Space。它的源码位于本仓库spaces/vision-demo/发布动作由 scripts/publish-space.sh 完成scripts/publish-space.sh vision-demo这样协议、方法名、相机几何等与仓库保持单一事实来源避免拷贝漂移。为什么拉流的方向在这里行不通把相机画面通过 rendezvous会合服务拉过来意味着 WebRTC而家庭路由器后面的机器人 ↔ 数据中心的容器之间的 WebRTC必须有一个 relay 候选作为兜底。这个 Space 的历史上恰恰没有可用的 relay所有机器人出厂时携带的 relay 默认值指向一个没有 DNS 解析的主机名于是出现了文档记载的预期结果——信令通了媒体没通。该默认值已在 docs/design/remote-access-design.md §6 修复但即便修复反转方向仍然是这个 Space 想要的方案原因很实际relay 会以机器人所有者的计量配额为代价来转发每一帧relay 流量按 Hugging Face 账号每月 10 GB 计量见 mediad/src/stream.rs 模块文档而这些帧只被一个程序解码观看不值得走一条按流量计费的中继通道。反转方向rendezvous 只传指令不传像素于是方向被反转了README 用一张图说明整个拓扑this Space ──media.stream {url: wss://…/frames}──► rendezvous ──► the duck the duck ═════════ H.264, outbound wss, direct ═════════════════► this Space即Space 通过 rendezvous 告诉机器人把帧发到哪个地址机器人从这个 Space向外发起 WebSocket 连接把 H.264 帧直连推过来。外向 WebSocket 是唯一永远可用的东西——机器人每秒钟能保持可达靠的正是它本来就一直握着一条到某个 Space 的外向连接这正是它出现在你的机器人列表里的原因。这样一来NAT 不再是参与者不再需要任何 NAT 穿透协商不需要凭证代理握手直接用机器人自己的账号 tokenrendezvous 传递的是指令而不是像素每个会话只有一条小指令包经过共享服务真正的字节流点对点直达——这正是它能在依赖同一服务的 mini 机队规模下扩展的原因。media.stream一个 key 的三种读法media.stream是这个方案的唯一开关由mediad自身应答实现见 mediad/src/stream.rs。同一个url键有三种读法请求含义media.stream {url: wss://…/frames}启动一条帧流media.stream {url: null}停止帧流media.stream不带url查询当前在流什么、流得怎么样在 Space 端spaces/vision-demo/app.py 的start/stop函数发出去的实际参数是{url: url, fps: fps, longest: longest, quality: QUALITY}其中quality是 JPEG 专用的H.264 默认时位率由 pipeline 决定页面上不提供该旋钮。mediad的Streamer::start会校验 url 必须是ws://或wss://、quality 必须在 1..100并在发送任何东西之前把目标 url 以 info 级别记入日志——把相机交给一个被告知的地方必须可审计。这种设计的代价与取舍反转方向不是免费的README 明确列了三笔代价随后是逐条源码佐证。没有返回媒体路径不能驱动机器人也不能实时看它。想看实时画面的观众需要 WebRTC那是 docs/design/remote-access-design.md §6 覆盖的场景。这个方案的适用对象是消费者是一个程序——模型、检测器、离线分析器而不是人。要控制机器人启动/停止流、询问状态走的是独立的 JSON-RPC 控制通道而非这条媒体路径。每秒几帧而不是三十帧每秒 5 帧、最长边 640px 对模型来说绰绰有余而且只是视频轨道本身编码量的一小部分。关键在于速率是由机器人一侧的videorate施加的而不是靠接收方礼貌地请求——在 mediad/src/stream.rs 中H.264 分支的Config::interval()直接返回Duration::ZERO因为 H.264 单元是编码器发出的速率已经由 pipeline 里的videorate限好了只有按需编码的 JPEG 分支才由这个线程自行守节奏fps.clamp(0.2, 15.0)决定间隔。Space 端默认值与之对齐spaces/vision-demo/app.pyFPS float(os.environ.get(DUCK_FPS, 5)) LONGEST int(os.environ.get(DUCK_LONGEST, 640)) QUALITY int(os.environ.get(DUCK_QUALITY, 70))页面上这三项frames a second / longest side (px)可调且mediad侧的Config常量默认值完全一致DEFAULT_FPS 5.0、DEFAULT_LONGEST 640、DEFAULT_QUALITY 70。H.264 为主JPEG 仍可回退板载硬件编码器意味着 H.264 消耗的是 VPU 而非 CPU 核心帧间预测带来的字节节省也很可观文档记录在合成帧上测得 H.264 单帧约 0.5 KB而 JPEG 约 6.5 KB——真实相机画面不会有这么夸张但方向一致。H.264 的代价是状态性接收方在关键帧到达之前什么都解不出来而丢失一个单元会污染其后所有单元直到下一个关键帧。mediad的处理方式不是祈祷而是三项落实阀门一开就向编码器要一个关键帧pipeline::force_keyframe每个关键帧前重复携带 SPS/PPS对应h264parse config-interval-1遇到缺口直接放弃到下一个关键帧而不是把缺口之后的预测帧发过去。最后这条在 mediad/src/stream.rs 里就是awaiting_key标志与dropped计数器的逻辑一旦丢过东西H.264 分支会丢弃一切直到关键帧a_dropped_h264_unit_is_followed_by_a_wait_for_a_keyframe测试精确验证了丢一个 P 帧就等关键帧的丢弃策略。因为每帧都要做颜色转换和 JPEG/单元编码、且 CPU 还要跑控制回路帧流同时最多一条第二个media.stream会替换第一个这也是不经过 stop 改变速率的唯一方式。对频繁重连、更在意立即起播而非带宽的接收方media.stream {encoding: jpeg}是另一条路。JPEG 每条消息独立可解天然是 keyframespaces/vision-demo/receiver.py 的jpeg_decoder()因此是无状态的一张图进、一张图出而h264_decoder()用av.CodecContext.create(h264, r)持有一个贯穿连接生命周期的有状态解码器按到达顺序喂入parse先剥 start code、把粘在关键帧前面的参数集拆开。帧到达时已经竖直相机安装角度差了四分之一圈但帧到达时已经转正。转换发生在mediadpipeline 的 tee 之前所以视频轨道和这条分支都携带竖直画面hello 里写rotate: 0同时把安装角mount_rotate作为参考信息一并给出mediad/src/stream.rs 的hello()函数。因此 spaces/vision-demo/filters.py 里的upright()在这条帧流路径上不会被用到——它保留下来是因为对 WebRTC 消费者仍然正确那边拿到的是相机原图需要按media.video告知的角度自行旋转。filters.upright()实现的是MOUNT_ROTATION_DEGREES 90的顺时针旋转np.rot90按逆时针计数所以取负并把np.ascontiguousarray收尾以保证内存布局连续。这是谁的相机账号隔离与认证帧端点GET /framesWebSocket是公开的——Space 的 URL 就是 Space 的 URL。所以机器人在握手时出示它自己的账号 tokenSpace 端通过whoami-v2解析出用户名与 rendezvous 的做法完全一致spaces/vision-demo/receiver.py 里whoami()与 rendezvous 的validate_hf_token是同一个调用。为什么必须做这一步README 说得很直白没有它任何人都能往演示里推帧更糟的是访客可能看到陌生人的相机。所以帧按机器人应答出的用户名归档Frames以(username, robot)为键一人多机不会串访客只被展示自己账号名下的机器人页面逻辑见 spaces/vision-demo/app.py 的render()receiver.FRAMES.newest(who)只读当前账号token每次连接只解析一次从不存储——进程里留下的是用户名。从源码看握手鉴权的顺序是先验后收receiver.py 的/frames处理器在accept()之前检查Authorization: Bearer头并解析whoami-v2拒绝时以 HTTP 403 / close code 1008 直接关闭——文档特意强调拒绝要大声说出来因为从机器人侧看关闭看起来就像url 写错了。自己运行本地复现与环境变量README 给出了完整的本地运行方式。一次性准备依赖uv venv uv pip install -r requirements.txt然后启动DUCK_RECEIVERws://192.168.1.50:7860/frames uv run app.py关键环境变量与语义如下表均来自 spaces/vision-demo/app.py 的读取逻辑环境变量默认值含义DUCK_RECEIVER见下告诉机器人把帧发到哪。必须是机器人能到达的地址——你机器在机器人网络里的地址而不是localhost在 Space 上由SPACE_HOST推导wss://{SPACE_HOST}/frames无需设置SPACE_HOST无Space 自身的 hostname在本地未设置时接收地址回退为ws://127.0.0.1:{PORT}/frames正好适合本机跑scripts/duck-sim的仿真鸭HF_TOKENhf auth login存储值本地代替登录按钮的凭证DUCK_FPS5每秒帧数DUCK_LONGEST640最长边像素DUCK_QUALITY70JPEG 质量H.264 时无效DUCK_LOGINFO日志级别DEBUG可看到控制通道收发细节PORT7860服务端口须与 README frontmatter 的app_port: 7860一致REACHY_CENTRAL_URLrendezvous 默认地址覆盖会合服务基址spaces/vision-demo/rendezvous.pyREADME 特别提醒DUCK_RECEIVER写成localhost只对同一台机器上的仿真鸭成立——真实机器人的 loopback 是它自己的写localhost等于让机器人拨号拨到它自己。页面上的地址输入框正是为这个场景准备的而登录按钮在 Space 之外是被 mock 的Gradio 会塞入字面量mock-oauth-token-for-local-dev任何服务都会正确拒绝它所以本地要用HF_TOKEN或hf auth login存储的凭证——这个坑在 spaces/vision-demo/app.py 的token_of()里有专门处理。为什么是 Docker SpaceFastAPI 持有服务器Space 的 frontmatter 声明了sdk: docker。原因不是偏好而是技术约束帧到达在一条属于我们自己的 WebSocket 路由上所以 FastAPI 拥有服务器Gradio 被挂载进 FastAPIapp gr.mount_gradio_app(app, demo, path/, ssr_modeFalse)。文档写明的方向是 FastAPI 在外、Gradio 在内——反向往 Gradio 的 app 上加路由有已知的 WebSocket 破坏问题而且这种问题在 Space 内部发现会异常恼人。自己跑uvicorn还消除了平台会不会承载自定义路由的任何疑问。microduck-console因另一条原因docs/design/remote-access-design.md §5.0也是 Docker Space所以这是项目里第二个。依赖清单spaces/vision-demo/requirements.txt也反映了这一架构gradio[oauth]5注意是[oauth]缺了itsdangerous/authlib会在 import 时失败而不是第一次点击时、fastapi、uvicorn[standard]、websocketsuvicorn 跑ws://路由需要它缺了机器人握手会得到一个像url 写错的 404、avH.264 解码、opencv-python-headless容器无显示headless 版免去整棵 GTK、numpy、requests、huggingface_hub。这里面已经没有任何 WebRTC 栈这个 Space 曾经用reachy_mini[central-consumer]拉帧那意味着aiortc、av和一个骑在对方私有方法上的 DTLS 密码套件垫片——而且它从数据中心永远连不上。现在机器人主动拨号剩下的是gradio、fastapi、opencv、requests和av。最后那个av是为 H.264 回来的但它是解码器而非传输层没有 ICE没有 DTLS没有信令没有任何 NAT 能插嘴的东西。控制侧同样干净控制通道走 spaces/vision-demo/wire.py 的WsConsumer用两个 HTTP 动词加一条 SSE 流完成与机器人的 JSON-RPC 会话——POST /send发、GET /events收Authorization: Bearer鉴权startSession的应答在 POST 响应体里而不是流上这是读该协议最容易错一次的地方。从 spaces/vision-demo/control.py 看Rpc维护独立 id 空间与 pending 表是传输无关的datachannel、LAN datachannel、纯 HTTP JSON-RPC 三种传输都喂同样的行进来页面代码一次都没改过。源码级细节四个文件串起一条帧流机器人列表rendezvous.pyspaces/vision-demo/rendezvous.py 用一次GET /api/robot-status列出账号名下的机器人对应meta.kind microduck的条目并解析出peer_id、名称、release、simulated仿真鸭来自configd --simulated、busy被谁占用activeApp。它特意不开任何会话——列表刷新不会挤掉正在持有的会话401只意味 token 不被whoami-v2认识而429则不是 rendezvous 写的该路由从不做 rate-limit 检查_who_answered()会把你带到应答里那些可引用的头server、retry-after、cf-ray…来定位是哪个边缘节点拦的。User-Agent必须诚实自报家门否则会被边缘当作 python-requests 机器人拦成 429。帧流接收receiver.pyspaces/vision-demo/receiver.py 是机器人拨号落地的那个 socket先收一条文本 hello描述编码、尺寸、速率、机器人身份之后每个二进制消息就是一个访问单元。解码按 hello 里的frames.encoding分支不嗅探字节JPEG 无状态、H.264 有状态。超过STALE_AFTER 5.0秒没新帧的流标记为 stale 并在页面上如实展示——流停了和从来就没有流是两回事。帧上跑什么filters.pyspaces/vision-demo/filters.py 只依赖 OpenCV 和 numpy所以可以脱离机器人、token、WebRTC 在合成帧上单独验证。FILTERS字典提供五个选项raw、edges (Canny)灰度→Canny(80,180)→黄色蒙版叠加、motion帧差阈值 18膨胀→红色蒙版、optical flow200 个角点的 Lucas–Kanade 稀疏光流位移平方小于 4 的亚像素抖动被滤掉、camera geometry从传感器光学反推fxfy、主点与视场角并绘制叠加文档注明推导而非标定media.video才是真相。机器人端mediad/src/stream.rs机器人那一半的完整闭环在 mediad/src/stream.rsStreamer::start校验配置→开 H.264 阀门→起编码线程与pumppump拨号→发 hello→carry转发帧同时读回端模型若有回话会计数并对断连做 2 秒起、30 秒封顶、20% 抖动的指数退避重拨——因为Space 每次 push 都会重启、无人看时还会休眠接收方消失是常态而非异常。三个测试精确钉住了协议形态frames_reach_a_receiver_the_robot_dialledhello 先行、bearer 携带、a_receiver_that_hangs_up_is_redialled重拨后新连接会收到新的 hello、the_hello_names_the_robot_where_the_receiver_lookshello 的robot.name字段形状与 spaces/vision-demo/receiver.py 读取处互锁。容器启动与故障诊断boot.py 与 Dockerfile最后是部署侧的工程细节。spaces/vision-demo/boot.py 在try里 import 应用失败时不退出而是起一个只回显完整 traceback 与关键环境变量的 FastAPI——Space 保持存活、curl /直接回答为什么起不来省去容器退出→平台只报首行截断→要写权限才能读日志的来回。而 spaces/vision-demo/Dockerfile 的注释记录了两个血泪教训GRADIO_SSR_MODEfalseSSR 会起一个 Node 服务器这个镜像没有 Node且登录页后面不需要 SEOSYSTEMspacesGradio 判断是否在 Space 上用的是os.getenv(SYSTEM) spaces且还要SPACE_IDDocker Space 默认不设SYSTEM于是 Gradio 装上 mocked 登录路由import 时因没有本地hf auth login直接ValueError——SPACE_ID和OAUTH_CLIENT_ID一直都在缺的只有这一行。小结vision-demo是一份把机器人外呼这一朴素事实用到极致的参考实现外向 WebSocket 永远可用所以媒体也走外向rendezvous 只传递指令账号 token 只做身份解析、从不落地H.264 的状态性用关键帧策略正面处理而不是回避。无论你是想把它跑成本地的模型推理演示、还是想把同样的机器人拨号模式复用到自己的消费端程序spaces/vision-demo/ 下的app.py、receiver.py、wire.py、rendezvous.py、filters.py与机器人侧的 mediad/src/stream.rs 合在一起就是一份从协议到部署都可以对照着抄的完整答案。赞分享机器人嵌入式强化学习人工智能智能硬件计算机视觉音视频【免费下载链接】microduckA Tiny biped duck robot 项目地址https://gitcode.com/gh_mirrors/mi/microduck点击查看免费下载相关推荐LineFit地面分割算法让机器人看清地面的核心技术解析LineFit地面分割算法让机器人看清地面的核心技术解析 在自动驾驶和机器人导航领域如何让机器看清地面是至关重要的基础能力。LineFit_Ground自动驾驶计算机视觉智能桌面机器人开发全攻略5大核心技术让你的机器人活起来智能桌面机器人开发全攻略5大核心技术让你的机器人活起来 想象一下你的桌面上有一个能够眨眼、微笑、甚至根据你的手势做出反应的智能伙伴。这不是科幻电影而是智能硬件机器人嵌入式硬件开发在 Convex 中不借助打包器直接连接后端HTML Demo 全面解析在 Convex 中不借助打包器直接连接后端HTML Demo 全面解析 导读 Convex 是一个面向应用开发者的开源响应式数据库Reactive Dat数据库后端上一篇PerfKit Benchmarker配置完全手册YAML配置与参数覆盖详解下一篇在Linux系统中轻松部署eGPU的智能切换方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考