<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Williamtym · 探索手记</title><link>https://williamtymhuabian.github.io/personal-site/</link><description>Williamtym，苏州大学计算机科学与技术硕士研究生。记录技术实践、算法思考、竞赛经历与历史阅读。</description><language>zh-cn</language><atom:link href="https://williamtymhuabian.github.io/personal-site/feed.xml" rel="self" type="application/rss+xml"/><item><title>PickGo Lab 摄像头实践：从 RGB 识别走向深度感知</title><link>https://williamtymhuabian.github.io/personal-site/#/post/pickgo-lab-camera</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/pickgo-lab-camera</guid><pubDate>Sat, 17 Oct 2026 16:00:00 GMT</pubDate><description>接通浏览器与本机 OpenCV 的 RGB 识别链路，梳理 D435、D405、D455 的使用场景，并规划 RGB-D 对齐、三维定位与可回放实验的下一步代码。</description><content:encoded>&lt;p&gt;这次给 PickGo Lab 增加了 RGB 摄像头输入：浏览器采集彩色画面，本机 Python 调用原颜色与矩形识别函数，再将检测结果绘制到对应的送检帧上。&lt;/p&gt;
&lt;p&gt;真正需要理清的，是从“能看到画面”到“知道物体在哪里”之间的步骤。彩色视频、有效深度、相机坐标和机器人坐标，各自需要独立验证。这篇记录已完成的 RGB 接入，也整理下一步深度开发的具体路线。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href=&quot;https://github.com/williamtymhuabian/pickgo-lab&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PickGo Lab&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;一、先跑通 RGB 采集与识别&lt;/h2&gt;
&lt;p&gt;准备 Python 3.10 或以上版本，在本机安装依赖并启动视觉模式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/williamtymhuabian/pickgo-lab.git
cd pickgo-lab
python -m pip install -r requirements.txt
python -B app.py --vision-only&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用本机 Edge 或 Chrome 打开 &lt;a href=&quot;http://127.0.0.1:8767/vision.html?source=camera&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;视觉页面&lt;/a&gt;，Windows 也可以双击 &lt;code&gt;start-camera.cmd&lt;/code&gt;。此模式不需要 Node.js；博客本身只展示文章和图片，识别服务运行在读者自己的电脑上。&lt;/p&gt;
&lt;p&gt;先用合成图确认服务可用，再选择摄像头的 RGB 接口。点击“打开摄像头”后才申请视频权限，不采集麦克风。浏览器采集需要安全上下文，本机 localhost 属于可用于开发的环境。&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;getUserMedia 官方说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;当前开发记录中，D435 的 RGB 接口曾以 640×480 打开，并将画面交给原算法；当次结果为“未识别到目标”。它说明采集与送检已经连通，实物颜色识别仍需针对光照、颜色阈值和目标大小继续验证。真实深度尚未接入。&lt;/p&gt;
&lt;p&gt;这次补拍了一张 D435 RGB 真实画面，原始采集分辨率为 640×480。为避免展示背景人员与电脑屏幕，文章只使用 302×266 的橘子近景裁剪图。拍完即释放相机，没有采集音频。&lt;/p&gt;
&lt;figure class=&quot;article-figure&quot;&gt;&lt;a href=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-d435-rgb-real.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; aria-label=&quot;原尺寸查看：2026-10-18 D435 RGB 实拍：本地裁剪后的橘子近景；仅展示彩色采集，不代表目标识别或深度测距成功&quot;&gt;&lt;img src=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-d435-rgb-real.png&quot; alt=&quot;2026-10-18 D435 RGB 实拍：本地裁剪后的橘子近景；仅展示彩色采集，不代表目标识别或深度测距成功&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;2026-10-18 D435 RGB 实拍：本地裁剪后的橘子近景；仅展示彩色采集，不代表目标识别或深度测距成功&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;section-2&quot;&gt;二、预览在动，检测框要跟着送检帧&lt;/h2&gt;
&lt;p&gt;摄像头预览与实际送检图像分开展示。一次识别经过以下步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;RGB 视频 → 选择区域 → 等比缩放至 320×240 → PNG 送检
         → 本机 OpenCV → 原 det_color / det_rect
         → 核对 frame_id → 在对应送检帧上绘制结果&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连续识别在上一次返回后才采集下一帧，同一页面最多一个识别请求。切换来源、颜色或裁剪区域后，旧结果失效；未识别到目标时清空旧中心与角度。&lt;/p&gt;
&lt;figure class=&quot;article-figure&quot;&gt;&lt;a href=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-camera-test-stream.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; aria-label=&quot;原尺寸查看：RGB 页面联调截图：输入为浏览器合成测试视频流，未打开真实相机；用于检查预览、送检帧和原算法输出&quot;&gt;&lt;img src=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-camera-test-stream.png&quot; alt=&quot;RGB 页面联调截图：输入为浏览器合成测试视频流，未打开真实相机；用于检查预览、送检帧和原算法输出&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;RGB 页面联调截图：输入为浏览器合成测试视频流，未打开真实相机；用于检查预览、送检帧和原算法输出&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;这张图专门验证软件链路，不作为 D435 实拍效果。原函数含 0.4 秒等待，识别频率与预览帧率也应分开看。&lt;/p&gt;
&lt;p&gt;切换来源、关闭设备或离开页面会释放视频轨道。取消连续识别只暂停送检，仍保留预览。&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MediaStreamTrack.stop 说明&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;三、相机选型：先用已有 D435 完成闭环前的验证&lt;/h2&gt;
&lt;p&gt;我的第一选择是继续使用已有的 &lt;strong&gt;RealSense D435&lt;/strong&gt;。当前最需要补齐的是深度采集、配准和坐标链路，可以先用现有设备验证这些问题，再根据实测距离决定是否换机。&lt;/p&gt;
&lt;figure class=&quot;article-figure&quot;&gt;&lt;a href=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-d435-device.jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; aria-label=&quot;原尺寸查看：本机 RealSense D435 摄像头与三脚架的实拍照片&quot;&gt;&lt;img src=&quot;https://williamtymhuabian.github.io/personal-site/assets/projects/pickgo-d435-device.jpg&quot; alt=&quot;本机 RealSense D435 摄像头与三脚架的实拍照片&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;本机 RealSense D435 摄像头与三脚架的实拍照片&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;上图是本次使用的摄像头与三脚架实拍，展示设备的实际安装状态。&lt;/p&gt;
&lt;p&gt;按当前项目可能发展的场景，我会考虑三款具体型号。以下是选型建议，工作距离取自官方页面，实际表现还取决于材质、光照、分辨率和安装方式，规格核对日期为 2026-10-18。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;D435：现有设备优先。&lt;/strong&gt; 官方理想范围为 0.3—3 m，适合先做桌面物体观察、目标测距和 RGB-D 接口开发。其 RGB 是滚动快门，深度成像器是全局快门，运动场景需要留意彩色与深度的时序差异。&lt;a href=&quot;https://store.realsenseai.com/buy-realsense-depth-camera-d435.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;D435 官方规格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D405：明确需要近距离抓取时考虑。&lt;/strong&gt; 官方标准理想范围为 7—50 cm，适合腕部或近距离工位观察。它的彩色信息来自左侧成像器经 ISP 输出，选购前要确认颜色效果、安装距离和遮挡是否满足任务。&lt;a href=&quot;https://www.realsenseai.com/products/d405-series/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;D405 官方说明&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D455：移动底盘与较远场景时考虑。&lt;/strong&gt; 官方理想范围为 0.6—6 m，具备全局快门 RGB 与深度成像、集成 IMU；更适合需要较远环境观察的方向。它能否满足某个抓取距离仍要单独测试。&lt;a href=&quot;https://www.realsenseai.com/product-family/450-series/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;D455 官方说明&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建议先记录相机到目标的实际距离，再比较有效深度比例和测距误差。购买判断应由这些结果驱动；目前没有必要为了让项目看起来完整就一次购入多款设备。&lt;/p&gt;
&lt;h2 id=&quot;section-4&quot;&gt;四、深度相机开发：先做独立采集，再接回页面&lt;/h2&gt;
&lt;p&gt;浏览器获得的 RGB 视频不包含可直接读取的毫米深度。RealSense 深度开发应从官方 SDK 与 Python 包 &lt;code&gt;pyrealsense2&lt;/code&gt; 入手，先在 RealSense Viewer 中检查设备、RGB、深度和 USB 连接，再开发独立采集程序。&lt;a href=&quot;https://github.com/realsenseai/librealsense&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;官方 SDK 与安装入口&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;建议由 Python 统一持有 D435 的 RGB 与深度流，网页读取本机服务提供的帧和检测结果。这样可以避免浏览器和 SDK 同时争用同一设备，也方便保存同一组图像、内参和时间戳。&lt;/p&gt;
&lt;p&gt;第一步只做“像素对应的三维表面点”。下面是独立实验的最小示意，尚未接入项目，也未在本轮硬件上运行。示例配置需与设备支持的流模式匹配。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pyrealsense2 as rs

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
align = rs.align(rs.stream.color)

started = False
try:
    pipeline.start(config)
    started = True
    for _ in range(30):
        frames = pipeline.wait_for_frames(5000)
    aligned = align.process(frames)
    depth = aligned.get_depth_frame()
    color = aligned.get_color_frame()
    if not depth or not color:
        raise RuntimeError(&amp;quot;没有取得对齐后的 RGB-D 帧&amp;quot;)
    # 这里只测图像中心，不代表已经检测到了目标。
    u, v = color.get_width() // 2, color.get_height() // 2
    z_m = depth.get_distance(u, v)
    if z_m &amp;lt;= 0:
        raise RuntimeError(&amp;quot;该像素无有效深度，请换位置或检查场景&amp;quot;)
    intrinsics = depth.profile.as_video_stream_profile().intrinsics
    point_m = rs.rs2_deproject_pixel_to_point(intrinsics, [u, v], z_m)
    print(&amp;quot;相机坐标中的表面点 / m:&amp;quot;, point_m)
finally:
    if started:
        pipeline.stop()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里用 &lt;code&gt;rs.align(rs.stream.color)&lt;/code&gt; 把深度对齐到彩色图像，再读取匹配的内参。官方提供了 &lt;a href=&quot;https://github.com/realsenseai/librealsense/blob/master/wrappers/python/examples/align-depth2color.py&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;深度对齐 Python 示例&lt;/a&gt; 和 &lt;a href=&quot;https://realsenseai.github.io/librealsense/python_docs/_generated/pyrealsense2.align.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;align 接口说明&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;get_distance&lt;/code&gt; 返回米制深度；读取原始 Z16 数据时则必须使用实际 &lt;code&gt;depth_scale&lt;/code&gt; 转换，不能一律假定原始值就是毫米。深度伪彩图只用于显示，测距需要原始深度值。&lt;a href=&quot;https://realsenseai.github.io/librealsense/python_docs/_generated/pyrealsense2.depth_frame.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;depth_frame 接口说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;对齐解决空间像素对应关系，仍需记录并检查 RGB、深度时间戳。相机坐标的轴向为右、下、前，三维点单位为米。它到机器人基座的转换还需要外参；固定相机与腕部相机的标定流程也不同。&lt;a href=&quot;https://dev.realsenseai.com/docs/projection-in-realsense-sdk-2-0/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;官方投影与坐标说明&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;section-5&quot;&gt;五、接下来具体写哪些代码&lt;/h2&gt;
&lt;p&gt;我会按下面的依赖顺序推进。这些文件名是拟新增模块，当前尚未实现；每一步先验收，再进入下一步。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sensors/realsense_source.py&lt;/code&gt;：统一采集接口。&lt;/strong&gt; 打开指定设备，输出 RGB、原始深度、内参、深度比例、帧号与各流时间戳；断连和超时明确报错，退出时释放设备。验收标准是能保存一组可读取、元数据完整的 RGB-D 样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;vision/pixel_mapping.py&lt;/code&gt;：还原送检坐标。&lt;/strong&gt; 记录当前 320×240 送检过程中的裁剪区域、缩放和留边，把检测坐标逆变换回原始彩色像素。黑边内的点应拒绝；不能直接拿送检中心去索引原始深度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;vision/depth_target.py&lt;/code&gt;：目标区域测距。&lt;/strong&gt; 在对齐后的目标内部筛选有效深度，避开边界混入的背景和深度空洞，输出有效比例、离散程度与失败原因。选定有效表面像素后用它自身的深度反投影，保留采样位置；暂不把一个表面点宣称为完整物体位姿。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;calibration/camera_to_base.py&lt;/code&gt;：坐标转换。&lt;/strong&gt; 明确相机安装方式，保存有版本的外参与单位，在已知测量点上检查转换误差。标定缺失、过期或误差不满足任务要求时，只显示观测，不输出可执行抓取目标。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;experiments/rgbd_replay.py&lt;/code&gt;：先回放，再接控制。&lt;/strong&gt; 保存 RGB、Z16、元数据和算法输出，使没有相机时也能复现同一组检测。先向仿真提供带时间戳的估计位置，确认过期帧、无效深度和不可达点会被拒绝，再考虑实体机械臂。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;原 &lt;code&gt;legacy/utils.py&lt;/code&gt; 与颜色阈值继续保留，新的采集与坐标处理放在适配层。现在的 &lt;code&gt;det_rect&lt;/code&gt; 角度只是二维轮廓角度；真正抓取还要结合目标几何、表面法线或姿态估计、夹爪约束与运动规划。&lt;/p&gt;
&lt;p&gt;第一个可以独立完成的小目标是：&lt;strong&gt;在网页点击物体，显示对应三维表面点，并保存这次 RGB-D 观测。&lt;/strong&gt; 先用已知距离的静止物体测误差，再比较不同距离、光照和材质下的有效比例与抖动。&lt;/p&gt;
&lt;h2 id=&quot;section-6&quot;&gt;六、可以继续探索的三个小方向&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;让系统解释为什么看不准。&lt;/strong&gt; 当目标颜色占比不足、深度空洞多、帧过期或坐标标定缺失时，页面显示具体原因，并保留失败样本。它既是调试工具，也能成为持续更新的实验内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;让机器人为观察选择位置。&lt;/strong&gt; 固定视角看不清时，先在仿真中比较几个观察位，尝试改善可见面积和深度有效比例，再决定是否靠近或改变视角。可以从规则策略开始，记录移动代价与观测收益。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;给语言指令加上几何核验。&lt;/strong&gt; 例如“找到红色盒子，靠近后观察”。语言模块负责解析目标与步骤，视觉模块返回观测，几何模块核对距离和可达性，再由执行器决定动作。这可以作为向 VLN／移动操作延伸的小实验；目前属于项目设想，尚无研究新颖性或效果结论。&lt;/p&gt;
&lt;h2 id=&quot;section-7&quot;&gt;七、已经验证的范围&lt;/h2&gt;
&lt;p&gt;现有项目的 36 项测试已通过，覆盖运动核心、摄像头辅助、视觉接口与 MCP 集成。浏览器联调使用合成图与合成视频流，检查了帧标识、连续识别不积压、设备释放与移动端布局；这些结果不能代替真实深度验收。&lt;/p&gt;
&lt;p&gt;本次另从 D435 RGB 补拍一张实图，确认彩色采集可用；没有对橘子补做识别测试，也没有获取真实深度。下一步最有价值的进展，是把一张真实 RGB-D 观测变成有单位、坐标、时间戳和误差记录的三维估计，再让后续任务使用这个估计。&lt;/p&gt;
&lt;p&gt;源码与运行说明：&lt;a href=&quot;https://github.com/williamtymhuabian/pickgo-lab&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PickGo Lab&lt;/a&gt;。&lt;/p&gt;</content:encoded></item><item><title>PickGo Lab 实践：用浏览器、Python 和 MCP 串起机器人搬运仿真</title><link>https://williamtymhuabian.github.io/personal-site/#/post/pickgo-lab</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/pickgo-lab</guid><pubDate>Fri, 16 Oct 2026 16:00:00 GMT</pubDate><description>从底盘与机械臂的搬运流程出发，整理一个可离线演示、支持 Python 与 MCP 共用场景，并能验证原视觉函数的轻量项目。</description><content:encoded>&lt;p&gt;本文记录 2026-10-17 的版本。RGB 摄像头接入与后续验证见 &lt;a href=&quot;https://williamtymhuabian.github.io/personal-site/#/post/pickgo-lab-camera&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;摄像头与深度开发实践&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如果要验证一个“移动到目标附近、抓取物体、运输并投放”的机器人流程，除了看最终动画，还需要知道：动作推进到了哪一步，暂停后能否继续，不同控制入口看到的状态是否一致，以及视觉函数到底输出了什么。&lt;/p&gt;
&lt;p&gt;这次分享的 PickGo Lab，围绕这些问题整理了一个轻量实验环境。它包含浏览器里的底盘与机械臂运动学仿真、本机 Python 服务、MCP 工具入口，以及复用原代码的视觉验证页面。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href=&quot;https://github.com/williamtymhuabian/pickgo-lab&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;williamtymhuabian/pickgo-lab&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;当前项目适合学习和验证任务流程、初步几何可达性及接口联调。自动搬运使用简化场景中的几何位置；视觉检测可以单独验证，尚未接入搬运动作的反馈闭环。下面按实际运行方式介绍。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;一、先跑起来：离线演示与共享仿真&lt;/h2&gt;
&lt;p&gt;最简单的体验方式，是下载项目后用现代浏览器打开根目录的 &lt;code&gt;index.html&lt;/code&gt;。这个页面可以单文件离线运行，无需安装 Python、Node.js 或 npm 依赖。&lt;/p&gt;
&lt;p&gt;页面中可以选择红、蓝、黄方块，观察机械臂取物、托盘固定、底盘绕障运输和投放流程，也可以手动调姿、暂停、继续、停止和导出状态。&lt;/p&gt;
&lt;p&gt;如果希望网页、Python 脚本和 MCP 客户端控制同一个场景，则需要运行本机服务。先准备 Python 3.10+ 和 Node.js 18+，再执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/williamtymhuabian/pickgo-lab.git
cd pickgo-lab
python -B app.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器打开 &lt;a href=&quot;http://127.0.0.1:8767/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;本机运动仿真页面&lt;/a&gt;。服务仅监听本机地址，其他读者需要在自己的电脑上启动；这个地址不是公共在线演示。&lt;/p&gt;
&lt;p&gt;共享运动仿真不需要安装 NumPy 或 OpenCV，也不需要执行 &lt;code&gt;npm install&lt;/code&gt;。视觉功能的依赖可以按需安装。&lt;/p&gt;
&lt;p&gt;如果默认端口被占用，可以运行 &lt;code&gt;python -B app.py --port 8768&lt;/code&gt;，并把浏览器和 MCP 配置中的端口一起改成 8768。按 Ctrl+C 可以关闭服务。&lt;/p&gt;
&lt;h2 id=&quot;section-2&quot;&gt;二、一个搬运任务怎样推进&lt;/h2&gt;
&lt;p&gt;运动核心在 &lt;code&gt;simulator-core.js&lt;/code&gt; 中，包含简化四轴机械臂与差速底盘模型、正逆运动学、底盘 A* 寻路，以及任务状态机。&lt;/p&gt;
&lt;p&gt;一段搬运任务会经过取物、放置到托盘、收臂、移动、再次取物、投放和收拢等动作。把这些动作组织成状态机，便于观察中间状态，也能处理重复启动、暂停和取消。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RUNNING&lt;/code&gt;：任务正在推进。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PAUSED&lt;/code&gt;：暂停，保持当前位置，之后可以继续。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUCCEEDED&lt;/code&gt;：后置条件校验通过，目标已投放且机械臂已收拢。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FAILED&lt;/code&gt;：任务执行或后置条件校验失败。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CANCELED&lt;/code&gt;：任务被停止，保留当前物体状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里需要区分“停止”和“重置”。停止会保留当前状态；重置才恢复初始场景。持物过程中停止任务，不能直接假定物体已经回到原位。&lt;/p&gt;
&lt;p&gt;底盘路径规划使用 A*，并按障碍膨胀后的间隙检查路径；运动中也有收臂互锁和底盘碰撞边界检查。机械臂仍是简化运动学模型，没有完整的机械臂碰撞检测，也不计算抓取力、摩擦和惯性。&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;三、让网页、Python 与 MCP 使用同一份状态&lt;/h2&gt;
&lt;p&gt;项目中的控制链路可以概括为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;网页操作 ───────────────────────→ app.py
Python 脚本 → utils.py ─────────→ app.py
MCP 客户端 → mcp_server.py
                    ↓
                 utils.py ─────→ app.py
                                    ↓
                           simulator-worker.cjs
                                    ↓
                            simulator-core.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认共享模式下，&lt;code&gt;app.py&lt;/code&gt; 持有仿真进程，网页、Python 动作函数和 MCP 入口读取同一场景的状态。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instance_id&lt;/code&gt; 用来区分场景实例，&lt;code&gt;task_id&lt;/code&gt; 用来区分某一次搬运。发起任务后，接口会立即返回任务标识，再通过 &lt;code&gt;get_robot_state&lt;/code&gt; 查询运行结果。&lt;/p&gt;
&lt;p&gt;任务标识也用于避免误判：如果查询时 &lt;code&gt;task_id&lt;/code&gt; 已经改变或清空，说明原任务可能被重置或替换，不能据此宣布原任务成功。&lt;/p&gt;
&lt;p&gt;在共享模式下，关闭网页或断开 MCP 客户端，不会取消已经被服务接收的任务。需要停止时，应显式调用停止操作。&lt;/p&gt;
&lt;p&gt;如果只想用 Python 调用项目动作，可以先启动 &lt;code&gt;app.py&lt;/code&gt;，再打开第二个终端执行现成示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -B examples/pick_and_send.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个示例展示了初始化、启动红色目标搬运、按任务标识查询结果，以及超时或中断时停止对应任务的处理。&lt;/p&gt;
&lt;h2 id=&quot;section-4&quot;&gt;四、MCP 接入：把动作作为工具暴露出来&lt;/h2&gt;
&lt;p&gt;PickGo Lab 提供轻量的 stdio MCP 服务。下面是常见 &lt;code&gt;mcpServers&lt;/code&gt; 配置形式，脚本路径需要替换成你自己电脑上的项目绝对路径：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;pickgo-lab&amp;quot;: {
      &amp;quot;command&amp;quot;: &amp;quot;python&amp;quot;,
      &amp;quot;args&amp;quot;: [
        &amp;quot;-B&amp;quot;,
        &amp;quot;C:/projects/pickgo-lab/mcp_server.py&amp;quot;,
        &amp;quot;--server-url&amp;quot;,
        &amp;quot;http://127.0.0.1:8767&amp;quot;
      ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先运行 &lt;code&gt;app.py&lt;/code&gt;，再让客户端启动 &lt;code&gt;mcp_server.py&lt;/code&gt;。不同客户端的配置位置可能不同；仓库提供的是服务入口和配置示例，不能把这份 JSON 当作所有客户端通用的安装步骤。&lt;/p&gt;
&lt;p&gt;当前可用工具包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_capabilities&lt;/code&gt;：读取当前仿真能力和限制。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_robot_state&lt;/code&gt;：查询场景与任务状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grab_and_send&lt;/code&gt;：启动指定目标的搬运任务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pause_task&lt;/code&gt;、&lt;code&gt;resume_task&lt;/code&gt;、&lt;code&gt;stop_task&lt;/code&gt;：管理任务生命周期。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reset_scene&lt;/code&gt;：恢复初始场景。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;det_color&lt;/code&gt;：对传入图像运行原视觉检测函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，&lt;code&gt;grab_and_send&lt;/code&gt; 的工具参数可以是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;target&amp;quot;: &amp;quot;blue&amp;quot;,
  &amp;quot;speed&amp;quot;: 3
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标支持 &lt;code&gt;red&lt;/code&gt;、&lt;code&gt;blue&lt;/code&gt;、&lt;code&gt;yellow&lt;/code&gt;，速度支持 1、3、6。启动成功后要继续查询状态，不能把“工具调用已返回”当作“物体已搬运完成”。通信失败时也不自动重发动作，应先查询当前状态。&lt;/p&gt;
&lt;p&gt;当前服务实现经典 MCP 的 stdio/tools 子集，不是完整 MCP 实现。&lt;code&gt;task_id&lt;/code&gt; 是本项目的业务字段，不代表支持 MCP tasks 扩展；本机 &lt;code&gt;/api/*&lt;/code&gt; 接口也不是 MCP HTTP 传输端点。具体范围见仓库的 &lt;a href=&quot;https://github.com/williamtymhuabian/pickgo-lab/blob/main/docs/mcp.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MCP 接入说明&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另有 &lt;code&gt;--standalone&lt;/code&gt; 模式，会建立独立场景；它与已经打开的网页不是同一个场景。需要网页与工具同步时，使用前面的共享模式。&lt;/p&gt;
&lt;h2 id=&quot;section-5&quot;&gt;五、视觉验证：复用原函数，单独检查输入输出&lt;/h2&gt;
&lt;p&gt;如果需要视觉功能，在项目目录安装可选依赖后启动服务：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m pip install -r requirements.txt
python -B app.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后打开 &lt;a href=&quot;http://127.0.0.1:8767/vision.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;本机视觉验证页面&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;页面支持合成 RGB-D 测试图、本地 RGB 图片、目标颜色与位置变化，以及无目标、无效深度等情况。输出包括检测框、中心、像素偏差、原始矩形角度和有效深度。&lt;/p&gt;
&lt;p&gt;项目复用 &lt;code&gt;legacy/utils.py&lt;/code&gt; 中的 &lt;code&gt;det_color&lt;/code&gt;、&lt;code&gt;det_rect&lt;/code&gt; 和 &lt;code&gt;col_dict_daylight_shelf_1&lt;/code&gt;。适配层通过 AST 白名单提取指定定义，不导入整个原模块，避免触发原工程的 ROS、串口或机器人初始化。&lt;/p&gt;
&lt;p&gt;原文件按字节保留，来源清单记录了 SHA256，测试也会检查一致性。原代码与新实现的范围在 README 中分别说明，原始代码的归属沿用源项目。&lt;/p&gt;
&lt;p&gt;视觉验证当前使用可控二维 RGB-D 输入，没有生成三维场景的真实相机投影，也没有把检测结果用于自动搬运闭环。没有有效深度时，接口不会补造距离。这样可以先把函数本身的行为检查清楚，再讨论后续系统集成。&lt;/p&gt;
&lt;h2 id=&quot;section-6&quot;&gt;六、如何验证这个项目&lt;/h2&gt;
&lt;p&gt;仓库附带三组测试：运动核心、视觉接口和 MCP／共享状态集成测试。本次整理后，27 项现有测试已在本机运行通过。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;node tests/simulation.test.cjs
python -B tests/test_vision.py
python -B tests/test_mcp.py&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;运动测试 9 项：检查正逆运动学、绕障、三种目标搬运、互锁、暂停和取消等。&lt;/li&gt;
&lt;li&gt;视觉测试 9 项：检查颜色、中心、角度、有效深度、空轮廓和原文件完整性等。&lt;/li&gt;
&lt;li&gt;MCP 与集成测试 9 项：检查协议握手、工具参数、任务生命周期、本机接口，以及网页与 MCP 共用状态等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些测试验证的是当前程序的行为，不代表实体机器人上的效果。实际运行环境、边界条件和硬件接入仍需分别验证。&lt;/p&gt;
&lt;h2 id=&quot;section-7&quot;&gt;七、当前阶段与后续方向&lt;/h2&gt;
&lt;p&gt;这一版把离线演示、共享控制和视觉函数验证整理到了同一个项目中，方便分别运行、观察和复现。原生厂家 SDK、ROS 工程和真实设备后端没有包含在这个轻量仓库里。&lt;/p&gt;
&lt;p&gt;后续可以继续探索三件事：生成与三维场景一致的相机投影；让检测结果参与搬运反馈；在完善模型、限制和状态校验后，通过独立后端接入真实设备。这些属于后续工作，当前版本尚未实现。&lt;/p&gt;
&lt;p&gt;如果你对底盘与机械臂协作、任务状态机或 MCP 工具接入感兴趣，可以从离线页面开始，再尝试本机共享模式。也欢迎围绕具体输入、操作步骤和实际结果交流。&lt;/p&gt;
&lt;p&gt;源码与运行说明：&lt;a href=&quot;https://github.com/williamtymhuabian/pickgo-lab&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PickGo Lab&lt;/a&gt;。&lt;/p&gt;</content:encoded></item><item><title>从目标到行动：一个可以动手的机器人导航小实验</title><link>https://williamtymhuabian.github.io/personal-site/#/post/embodied-sandbox</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/embodied-sandbox</guid><pubDate>Thu, 15 Oct 2026 16:00:00 GMT</pubDate><description>给机器人设置目标，放置障碍，再观察它如何规划路径。用一个已知地图上的二维实验，开始理解环境、规划与行动。</description><content:encoded>&lt;p&gt;智能与环境之间，存在一个持续交互的过程：观察当前情况，决定下一步，执行动作，再查看环境中的新状态。&lt;/p&gt;
&lt;p&gt;网站加入了一个可以动手的二维导航实验。它没有使用真实机器人或学习策略，而是在已知网格地图上，用 BFS 搜索一条可达路径，再逐格执行移动。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;先给机器人一个目标&lt;/h2&gt;
&lt;p&gt;进入站内的具身实验室，点击空白格设置目标，再点击“规划并移动”。机器人会沿着路径逐步到达目标。&lt;/p&gt;
&lt;p&gt;地图中的障碍格无法穿过。起点和目标的位置改变时，搜索得到的路径也会改变。&lt;/p&gt;
&lt;h2 id=&quot;section-2&quot;&gt;改变环境，再观察结果&lt;/h2&gt;
&lt;p&gt;选择“编辑障碍”，点击地图上的格子添加或移除障碍。然后重新规划，观察路径步数和探索格数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保持目标不变，只增加一个障碍，看看路径是否绕行。&lt;/li&gt;
&lt;li&gt;把目标周围的可达格挡住，观察不可达提示。&lt;/li&gt;
&lt;li&gt;聚焦地图后，使用方向键手动移动机器人，再从新的位置规划。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几个操作分别改变环境、可达性与当前状态。它们能让“动作依赖于状态和环境”这件事更容易观察。&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;BFS 在这里解决了什么问题&lt;/h2&gt;
&lt;p&gt;实验把每个可通行格看成一个节点，相邻格之间可以移动，且每次移动的代价相同。BFS 按层扩展节点，在这样的有限无权图中寻找最少步数的路径。&lt;/p&gt;
&lt;p&gt;实验会记录搜索过程中首次到达节点时的前驱，找到目标后沿前驱链还原路径。执行阶段再依次移动到路径中的下一个格子。&lt;/p&gt;
&lt;h2 id=&quot;section-4&quot;&gt;这个实验没有覆盖什么&lt;/h2&gt;
&lt;p&gt;真实具身智能系统还需要处理观测噪声、不完整地图、动力学、碰撞约束、连续控制以及学习等问题。这个二维演示没有模拟这些复杂条件，也不代表任何实际科研结论。&lt;/p&gt;
&lt;p&gt;先明确实验的范围，再逐步增加新问题，比把一个简单动画称为完整智能系统更有助于学习。&lt;/p&gt;
&lt;h2 id=&quot;section-5&quot;&gt;动手试试&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://williamtymhuabian.github.io/personal-site/#/lab&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;进入具身智能交互实验室&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下一步可以继续尝试不同搜索方法、变化的障碍或局部可观测地图。每次只增加一个条件，记录发生了什么变化。&lt;/p&gt;</content:encoded></item><item><title>从本地到 GitHub：第一次推送个人网站</title><link>https://williamtymhuabian.github.io/personal-site/#/post/first-push</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/first-push</guid><pubDate>Thu, 15 Oct 2026 16:00:00 GMT</pubDate><description>从一次本地提交开始，把个人网站放进 GitHub，再用 GitHub Pages 让它拥有自己的网址。</description><content:encoded>&lt;p&gt;Git 负责记录本地版本，GitHub 用来保存远程仓库，GitHub Pages 则负责把仓库中的网页发布出来。完成一次发布，需要依次走过这三个环节。&lt;/p&gt;
&lt;p&gt;这是一篇随网站附带的入门手记。仓库名、邮箱等示例需要替换成自己的信息；下面的命令没有自动执行。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;先在本地生成网页&lt;/h2&gt;
&lt;p&gt;打开 PowerShell，进入这个项目文件夹，生成网页：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;Set-Location -LiteralPath &amp;#39;C:\Users\ASUS\Desktop\自媒体\personal-site&amp;#39;
node build.mjs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成后的 &lt;code&gt;index.html&lt;/code&gt; 可以直接双击打开。需要本地预览地址时，执行 &lt;code&gt;node build.mjs --serve&lt;/code&gt;，再访问终端打印的地址。&lt;/p&gt;
&lt;h2 id=&quot;section-2&quot;&gt;建立本地版本记录&lt;/h2&gt;
&lt;p&gt;确认当前目录就是 personal-site。对于这个尚未初始化的新项目，依次执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;git init -b main
git config user.name &amp;quot;Williamtym&amp;quot;
git config user.email &amp;quot;替换成你的 GitHub 提交邮箱&amp;quot;
git status
git add .
git commit -m &amp;quot;Create personal website&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;邮箱可以在 GitHub 的 Settings → Emails 中查看，也可以使用那里提供的 noreply 邮箱。这里的 &lt;code&gt;git config&lt;/code&gt; 只设置当前仓库。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git add&lt;/code&gt; 选择准备记录的修改，&lt;code&gt;git commit&lt;/code&gt; 在本地保存一次版本。完成 commit 后，文件仍然只在本地。&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;连接 GitHub 仓库并推送&lt;/h2&gt;
&lt;p&gt;在 GitHub 创建一个名为 &lt;code&gt;personal-site&lt;/code&gt; 的空仓库。不要勾选自动添加 README、.gitignore 或 License，因为本地已经有文件。&lt;/p&gt;
&lt;p&gt;然后连接远程仓库，并推送 main 分支：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;git remote add origin https://github.com/williamtymhuabian/personal-site.git
git remote -v
git push -u origin main&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的远程地址以仓库名 personal-site 为例；如果使用其他名字，请换成新仓库页面显示的 HTTPS 地址。第一次连接时按 Git 的登录提示完成授权。GitHub 账号密码不能作为 HTTPS Git 推送密码；可通过 Git Credential Manager 的浏览器登录，或按官方说明配置令牌。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; 是远程仓库的习惯名称，&lt;code&gt;main&lt;/code&gt; 是分支名。首次使用 &lt;code&gt;-u&lt;/code&gt; 建立对应关系，以后在这个分支直接 &lt;code&gt;git push&lt;/code&gt; 即可。&lt;/p&gt;
&lt;h2 id=&quot;section-4&quot;&gt;把仓库变成网站&lt;/h2&gt;
&lt;p&gt;本项目已经包含生成后的网页。进入仓库的 Settings → Pages，选择 Deploy from a branch，再选 &lt;code&gt;main&lt;/code&gt; 和 &lt;code&gt;/ (root)&lt;/code&gt;，保存。&lt;/p&gt;
&lt;p&gt;对于 GitHub Free，使用公开仓库可以启用 Pages。查看 Actions 中的发布进度，成功后以 Pages 页面显示的网址为准。若使用 personal-site 这个仓库名，通常对应 &lt;code&gt;https://williamtymhuabian.github.io/personal-site/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果希望网址直接是 &lt;code&gt;https://williamtymhuabian.github.io/&lt;/code&gt;，则创建同名仓库 &lt;code&gt;williamtymhuabian.github.io&lt;/code&gt;，并相应修改远程地址。&lt;/p&gt;
&lt;h2 id=&quot;section-5&quot;&gt;以后更新只重复这几步&lt;/h2&gt;
&lt;p&gt;编辑 &lt;code&gt;profile.json&lt;/code&gt; 或 &lt;code&gt;content&lt;/code&gt; 中的 Markdown 文件后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;node build.mjs
git status
git add .
git commit -m &amp;quot;Update notes&amp;quot;
git push&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;node build.mjs&lt;/code&gt; 把文章改动写入网页，&lt;code&gt;git push&lt;/code&gt; 把新版本发送到 GitHub。Pages 开启后会根据新的推送再次发布。&lt;/p&gt;
&lt;p&gt;如果提示没有新内容可以提交，先用 &lt;code&gt;git status&lt;/code&gt; 检查是否确实修改并保存了文件。如果提示远程已有更新，先查看双方差异；不要用强制推送覆盖不明来源的提交。&lt;/p&gt;
&lt;h2 id=&quot;section-6&quot;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub 官方：添加本地代码到 GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub 官方：配置 Pages 发布来源&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub 官方：身份验证方式&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>写一个小实验：把二分查找的边界看清楚</title><link>https://williamtymhuabian.github.io/personal-site/#/post/binary-search</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/binary-search</guid><pubDate>Thu, 15 Oct 2026 16:00:00 GMT</pubDate><description>用一个有序数组和一段短代码，观察区间如何缩小。先约定边界，再理解每一步为什么成立。</description><content:encoded>&lt;p&gt;二分查找的代码很短，但边界很容易写乱。与其记住一份模板，不如选定一种区间约定，再用一个小实验检查它。&lt;/p&gt;
&lt;p&gt;下面是开站时附带的算法示例笔记，可以继续补充自己的测试与理解。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;先说明我们要找什么&lt;/h2&gt;
&lt;p&gt;给定一个非递减排列的数组，找到第一个大于等于目标值的位置。如果不存在，就返回数组长度。&lt;/p&gt;
&lt;p&gt;这个定义可以同时处理重复元素和找不到目标的情况。例如，在 &lt;code&gt;[1, 3, 3, 7, 9]&lt;/code&gt; 中查找 3，答案是下标 1；查找 4，答案是下标 3；查找 10，答案是 5。&lt;/p&gt;
&lt;h2 id=&quot;section-2&quot;&gt;始终使用左闭右开的区间&lt;/h2&gt;
&lt;p&gt;将还没有检查完的区间记为 &lt;code&gt;[left, right)&lt;/code&gt;。开始时 &lt;code&gt;left = 0&lt;/code&gt;，&lt;code&gt;right = a.size()&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果中间元素小于目标值，那么它和它左边的元素都不可能是答案。&lt;/li&gt;
&lt;li&gt;否则，中间元素有可能是答案，继续检查更靠左的位置。&lt;/li&gt;
&lt;li&gt;当左右边界相遇时，剩余区间为空，当前位置就是答案。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;cassert&amp;gt;
#include &amp;lt;vector&amp;gt;

std::size_t first_not_less(const std::vector&amp;lt;int&amp;gt;&amp;amp; a, int target) {
    std::size_t left = 0;
    std::size_t right = a.size();
    while (left &amp;lt; right) {
        const auto mid = left + (right - left) / 2;
        if (a[mid] &amp;lt; target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

int main() {
    const std::vector&amp;lt;int&amp;gt; a{1, 3, 3, 7, 9};
    assert(first_not_less(a, 3) == 1);
    assert(first_not_less(a, 4) == 3);
    assert(first_not_less(a, 10) == 5);
    assert(first_not_less({}, 3) == 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;section-3&quot;&gt;观察过程，比只看结果多一步&lt;/h2&gt;
&lt;p&gt;可以在循环中打印 &lt;code&gt;left&lt;/code&gt;、&lt;code&gt;mid&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt;，分别输入小于最小元素、恰好相等、落在两个元素之间和大于最大元素的目标值。&lt;/p&gt;
&lt;p&gt;观察时问自己：这一步排除了哪些位置？留下的位置为什么仍然包含我们要找的边界？数组有序是这个过程成立的前提。&lt;/p&gt;
&lt;h2 id=&quot;section-4&quot;&gt;给实验补上自己的理解&lt;/h2&gt;
&lt;p&gt;可以继续比较 &lt;code&gt;std::lower_bound&lt;/code&gt; 的结果，或把需求改为“第一个严格大于目标值的位置”。不要同时改变区间约定和查找目标：一次只改一件事，更容易发现边界变化的原因。&lt;/p&gt;</content:encoded></item><item><title>开一个小站，给探索留一个位置</title><link>https://williamtymhuabian.github.io/personal-site/#/post/hello-world</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/hello-world</guid><pubDate>Thu, 15 Oct 2026 16:00:00 GMT</pubDate><description>从今天起，把技术实践、算法思考和历史阅读放进同一本手记。一次记录一点，慢慢积累。</description><content:encoded>&lt;p&gt;你好，我是 Williamtym，目前在苏州大学攻读计算机科学与技术硕士，关注具身智能。&lt;/p&gt;
&lt;p&gt;我对新技术感兴趣，也喜欢历史。开始这个网站，是想给自己的学习和探索找一个固定的地方，并试着在表达中发现自己还能做什么。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;这里准备记录什么&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;技术实践：&lt;/strong&gt; 从一个具体问题出发，记录尝试、结果和仍然存在的限制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法思考：&lt;/strong&gt; 把概念、例子和代码放在一起，慢慢把问题讲清楚。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史阅读：&lt;/strong&gt; 从感兴趣的小问题开始，重视出处，也保留疑问。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习随记：&lt;/strong&gt; 留下阶段性的想法，方便以后回头看看。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;section-2&quot;&gt;先做一件小事&lt;/h2&gt;
&lt;p&gt;每周能投入的时间有限，所以希望从一篇范围明确的内容开始。把一个问题讲完整，比同时开很多题目更适合作为起点。&lt;/p&gt;
&lt;p&gt;不必等到所有事情都想明白再动笔。重要的是把已知的依据说明白，把还不确定的地方标出来，并在新的实践中继续修正。&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;把不同地方连接起来&lt;/h2&gt;
&lt;p&gt;GitHub 用来保存代码，CSDN 用来分享技术文章。这个网站则作为个人入口，慢慢整理文章、作品与经历。&lt;/p&gt;
&lt;p&gt;保持好奇，认真求证。希望下一次回来时，这里又多了一点值得留下的东西。&lt;/p&gt;</content:encoded></item><item><title>读历史时，先把事实与解释分开</title><link>https://williamtymhuabian.github.io/personal-site/#/post/reading-history</link><guid isPermaLink="true">https://williamtymhuabian.github.io/personal-site/#/post/reading-history</guid><pubDate>Thu, 15 Oct 2026 16:00:00 GMT</pubDate><description>留下一个阅读习惯：看见一个有趣的说法时，先追问它来自哪里，再判断它能解释多少事情。</description><content:encoded>&lt;p&gt;历史的吸引力，不只在于发生过什么，也在于同一件事为什么会被不同的人讲成不同的故事。&lt;/p&gt;
&lt;p&gt;这篇开站示例笔记先记下一种阅读方式。以后可以围绕具体书目和史料，把问题继续展开。&lt;/p&gt;
&lt;h2 id=&quot;section-1&quot;&gt;把笔记分成三层&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;材料说了什么：&lt;/strong&gt; 记录出处、原文位置和大致语境。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作者怎样解释：&lt;/strong&gt; 标记推断、因果判断和价值评价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我还有什么疑问：&lt;/strong&gt; 写下暂时没有证据回答的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把三层分开，能帮助自己在重读时知道：哪些是材料提供的信息，哪些是作者的理解，哪些仍然只是自己的猜想。&lt;/p&gt;
&lt;h2 id=&quot;section-2&quot;&gt;遇到很顺的故事，多问一个问题&lt;/h2&gt;
&lt;p&gt;一段叙事如果把复杂事件解释得非常顺畅，可以先问：它忽略了哪些人、哪些条件、哪些相反的材料？&lt;/p&gt;
&lt;p&gt;这并不意味着生动的表达一定不可靠，而是提醒自己，表达的感染力与证据的充分程度，需要分别判断。&lt;/p&gt;
&lt;h2 id=&quot;section-3&quot;&gt;让一次阅读留下一个可追问的问题&lt;/h2&gt;
&lt;p&gt;与其急着写一个大结论，不如先留下一个小问题：某个说法最早出现在哪里？不同作者是否使用了同一份材料？翻译和转述有没有改变原意？&lt;/p&gt;
&lt;p&gt;等到资料足够，再把这个小问题整理成一篇有出处、边界清楚的文章。暂时不知道的地方，也值得诚实记下来。&lt;/p&gt;</content:encoded></item></channel></rss>
