项目作品

BOX · 生产物料拉动系统

把现场扫码、仓库补料、配送标签和收货确认接在同一条任务流程里,让每一次叫料都有状态、有记录,也能找到对应的物料和工位。

Versigent(维智捷)· 芜湖 / 工作项目 · 2026.04 - 至今 / Java 17 · Spring Boot · JPA · MySQL

背景与目标

生产现场缺料时,最急的事情是把正确的物料送到正确的工位。过去,这个动作常常要经过口头叫料、仓库确认、配料和配送几次转述。物料号、仓库料号、工位地址和数量没有放在同一处,接收信息的人需要再次核对。等物料送出以后,现场还需要知道任务到哪一步,仓库也需要确认这一单是否已经结束。BOX 就是从这条日常流程开始做的。

我的目标是把一次叫料变成有编号、有状态、有操作记录的任务。现场扫码后,系统能够识别物料和工位,仓库能够集中接收补料需求,配送标签能够带着同一组信息继续流转,现场收货后能够确认闭环。这样,后续排查一次送错、漏配或重复叫料时,不必从头问一圈人,而是先找到对应任务和它的操作过程。

这是一项工作项目。我参与现场需求梳理和系统建设,负责前后端、数据库及微信小程序的开发维护,也参与服务器部署、问题排查和版本迭代。项目仓库还包含 Android 扫码端、打印代理和部署资料。不同客户端服务于同一条物料流程,但仓库存在多个版本,代码中的能力与现场实际启用的功能需要分别说明。

现场问题与业务约束

不同编码需要落到同一笔业务

现场叫得出口的物料号、仓库使用的料号、打印在标签上的编码,不一定是同一种标识。扫码枪读到的也不总是一串干净的数字,可能带回车、Tab、BOM,也可能读出 JSON 或键值对内容。因此,系统不能把扫描文本直接当成唯一业务编号;它需要先整理输入,再查找候选标签,最后确认是否存在明确、唯一的匹配。

另一类问题来自连续操作。操作员可能再次扫同一个码,设备可能重试网络请求,仓库任务还没有出发时也可能收到同一份需求。如果每次请求都直接新增任务,看板上的数量就会失真,仓库也容易重复配料。扫码入口必须区分一次请求的重复提交和同一标签的重复业务需求,并在生成任务之前处理这些情况。

双盒物料又多了一层状态约束:正在使用的盒子扫空后,备用盒需要能够接上。不是任意两个盒子都可以切换,配对关系、当前使用状态和备用盒满料状态都需要校验。若数据不完整或备用盒还未准备好,继续执行会把异常带到现场。因此,这一段逻辑需要把正常流转与异常停止写得同样清楚。

技术架构与职责分工

Spring Boot 负责业务,Vue 负责操作界面

后端使用 Java 17 和 Spring Boot,按 Controller、Service、Entity、Repository 组织代码。Controller 接收扫码、任务、标签和导入请求,Service 处理状态转换与业务规则,JPA Repository 负责数据访问。MySQL 用于业务数据存储,项目依赖也保留了 H2。这样的分层让界面调整、业务条件变化和数据库操作不必挤在同一个接口方法里。

前端使用 Vue3、TypeScript、Vite 和 Element Plus,结合 Axios 调用业务接口,ECharts 支持数据展示。任务、物料、映射和标签等页面围绕现场及仓库的操作展开,而不是只展示一组统计数字。前后端之间通过明确的数据结构传递任务编号、状态和关联信息,使列表、操作入口和后端校验能够对应到同一笔业务。

实时变化通过 Spring 的 WebSocket 消息通道推送。任务生成、打印任务提交等动作能够发布到对应主题,减少看板完全依赖手动刷新。推送服务同时记录 Outbox 信息,失败时留下原因。这里的价值是让业务操作和界面更新有连接,也保留排查线索;它并不意味着网络故障时的消息已经获得端到端必达保证。

部署方面,master 分支保留了 Ubuntu、MySQL、Spring Boot 和 Nginx 的运维说明。Nginx 提供前端静态文件,并将 API 和 WebSocket 请求反向代理给本机后端,浏览器通过同一入口访问。服务启动、日志查看、版本更新和地址变化都需要纳入交付考虑。对现场工具来说,能开机恢复、能找到入口、出问题能定位,与页面功能同样重要。

关键功能与实现

扫码解析与业务匹配

LabelResolverService 先清理扫描内容边界和控制字符,限制异常长度,再从普通内容、JSON 字段、键值对和启用的规则中提取查询候选。候选命中结果按标签记录去重,最后要求唯一匹配。找不到标签或找到多个标签时返回明确的业务错误,而不是随便取第一条。规则表提供了前缀、精确值、正则等匹配入口,便于后续增加码制时减少核心流程改动。

ScanService 在标签解析之后继续处理叫料。标签不存在时还会进入物料号叫料路径;标签存在时检查关键字段与状态,记录扫描时间、设备和操作员,并关联周转盒及补货任务。这个顺序把输入解析、基础数据检查和任务生成分开,避免一个看似有效的码在缺少数量、物料或送达工位的情况下继续流转。

重复请求与双盒切换

请求侧使用幂等键和请求摘要记录执行状态,区分正在处理、已完成和失败的请求;扫码业务还通过业务锁围绕同一个扫描对象执行。业务侧则查询尚未出发的补货任务,遇到重复扫码时返回已有任务编号并写入审计记录。这两层处理分别应对网络重试与现场重复操作,目的都是减少重复单据,而不是用前端按钮禁用来代替后端约束。

双盒流程要求配对数量正确,当前盒处于允许的状态,备用盒已经满料。正常情况下,当前盒转入等待补货,备用盒转为使用中,再建立对应补货任务;异常情况下记录健康状态、锁定原因和告警,并停止切换。这样的状态管理让“不能继续”的原因也成为业务数据,便于仓库或现场人员找到需要修正的位置。

任务流转、收货与异常

TaskService 把接单、开始配料、配料完成、配送、到达和完成等动作放进状态转换逻辑。每个动作先校验任务当前状态,再更新状态及相关信息。例如,不能把刚创建的任务直接当成已经送达,也不能忽略配料前的库存关联。ScanService 还提供现场收货确认,把扫描内容解析到配送任务,再交由任务服务处理闭环;填写空盒信息时,流程能够衔接空盒回收。

异常上报与正常收货使用不同入口,保留异常类型、原因、设备、人员和关联物料工位信息。系统会把异常状态反映到任务和大屏,而不是只在当前页面弹出一句错误。审计记录贯穿扫描、状态操作和打印提交,使后续分析能够还原“谁在什么设备上,对哪一项业务做了什么”,而不是只有最终结果。

标签、打印与批量导入

标签承载物料、数量、仓库、工位地址、人员、二维码、图片和使用状态等信息。打印服务建立独立打印任务,将业务任务与打印任务编号关联,并提供按状态查看的入口。对于已生成的打印任务,代码会检查既有记录,减少重复提交。二维码生成与表格处理分别使用 ZXing、Apache POI 和 Commons CSV 等依赖,服务于现场识别与基础数据整理。

ImportService 接受 Excel 和 CSV,对导入类型、文件扩展名与字段进行处理,整理物料、映射和标签等基础数据。CSV 会先转换成统一的工作表结构,处理 BOM 和空白,再沿同一套行解析逻辑执行。映射解析兼容带序号与不带序号的列布局,同时将料号、仓库代码、盒子规格、数量和工位组织到关联数据中。导入不是简单上传附件,它直接影响后续扫码是否能够正确匹配。

导入过程为每次上传建立批次,逐行处理时分别统计成功与失败,将错误行号、原始行内容和错误说明保存到对应批次。最后区分全部成功、部分成功和失败,使维护人员能够回到具体行修正数据,而不是看到一句“导入失败”就重新检查整个表格。这个机制也说明上传文件与数据真正进入业务之间还有校验过程,不能只凭文件被接收就确认导入完成。

打印端同样区分提交与完成。PrintJobService 接收状态回调,保存外部任务编号、打印时间或失败原因,并记录接口审计与推送更新;业务任务取消时,还会检查打印任务当前状态并处理取消。它把设备侧结果重新带回业务系统,帮助排查“任务已生成,但标签有没有真正打印”的问题,具体打印设备与代理仍需按部署环境验收。

分支中的权限与外部扫码扩展

fix/mapping-import-timeout 分支进一步限制映射导入的角色和数据范围。ImportController 对料号映射上传要求管理员权限,普通管理员只能操作对应范围;查看导入错误时也检查批次类型与操作人。这部分代码说明导入功能开始同时考虑“能否上传”和“能看哪一批结果”,不能把页面按钮是否可见当作完整权限控制。

同一分支还有外部扫码中继与 Linux Worker。Worker 从中继领取命令,把允许的扫码动作转换成内部 API 请求,将 requestId 透传为幂等键,再回报完成或失败;网络类失败与业务类失败有不同的重试处理。它提供了一条跨网络连接扫码操作的实现路线,但属于分支扩展,是否在现场使用仍要以部署记录为准,不能与 main 的现有业务能力直接合并成已上线结论。

完成目的与现场结果

项目把叫料信息、仓库任务和配送标签统一到同一条业务链路上。现场知道需求已经被记录,仓库知道需要配什么、送到哪里,配送和收货有状态可查,异常能够关联到具体任务。对我来说,最重要的交付不是多一个页面,而是把原本依靠转述的动作变成可查询、可校验、可维护的操作过程。

按此前个人提供的现场成果说明,叫料响应由约 10-15 分钟缩短到 3-5 分钟,仓库配料效率提升约 30%,人工沟通减少约 50%,配送异常减少约 40%,任务可追溯率达到 100%。这些数字属于现场成果记录,保留原来的来源;源码能说明系统采用了哪些机制,不能独立证明效率统计或追溯率的测量结果。

指标改善情况
叫料响应时间约 10–15 分钟 → 3–5 分钟
仓库配料效率提升约 30%
人工沟通次数减少约 50%
配送异常减少约 40%
任务可追溯率达到 100%

维护经验与后续方向

这项经历让我把业务建模、接口开发和现场问题放在一起考虑。一个标签字段缺失、一种二维码码制不统一、一次重复扫描,都可能影响整条流程。维护时需要先确认业务对象和当前状态,再查看请求、任务、打印与审计记录,最后判断是基础数据、操作条件、接口还是部署环境的问题。清楚的关联和错误信息能够减少排查时的猜测。

后续库存联动、AGV 配送和计划协同已有对应扩展空间,但进一步建设需要同时确认真实库存口径、设备接口、异常接管和现场责任边界。每个分支应分别核对版本与部署范围;外部扫码中继也需要结合实际网络、设备和重试场景验收。继续扩展的目的仍然是让物料流程更准确地运行,而不是把仓库中出现过的模块都写成已经投产的功能。

查看项目源码 ↗

校园助手 YU · Android 应用

把宿舍电费、课程和校园卡放进一个 Android 应用。既能查当前余额,也能留下历史记录、估算还能用多久,并把常用信息放到桌面上。

个人项目 · 独立开发 / Kotlin · MVVM · Jetpack Compose · Room

背景与目标

校园里经常要查的几件事,入口却不在同一处:宿舍电量在一卡通服务里,课程在教务系统里,校园卡余额和交易又是另一组页面。每天重复打开网页、登录和查找信息,操作本身并不复杂,但次数多了就显得零碎。我开发 YU,是想把这些与日常生活直接相关的内容整理到一个 Android 应用中,随手就能看到。

项目从“查询当前结果”继续往前走了一步。电费不仅需要显示余额,还需要保存历史,帮助判断消耗变化和大致还能用多久;课表不仅需要导入,还需要能够在桌面查看;校园卡不仅需要查卡片信息,也需要看到月账单、消费趋势和交易记录。信息留在本地后,工具才有机会提供连续的观察,而不是每次查询都重新开始。

这是我的个人 Android 项目,主要源码在 YU_2 的 master 分支。默认 main 分支只有发布说明,不能用它判断应用没有实现。我独立开展需求整理、架构和页面组织、功能开发与版本维护,围绕电费、课表、校园卡以及更新帮助等模块逐步完善。Python 版 YU 和小爱课表脚本是关联工具,各自的实现范围与 Android 应用分开展示。

使用问题与数据约束

余额变化不能直接当作用电量

电费估算的第一道问题是识别数据来源。余额下降通常对应消耗,余额上升可能是充值;两次记录间隔不同,下降金额也不能直接比较。如果把一次充值混进日均消耗,预计天数会偏离实际。因此,我把当前余额与历史记录分开处理,结合时间跨度整理下降记录和充值记录,再决定采用哪一段历史。

另一个问题是数据不足。刚设置宿舍的新用户可能没有连续历史,最近几天也可能记录较少。应用需要说明估算来自短期、中期、长期历史,还是充值模式与回退估计,不能所有情况下都给出同样确定的结果。预计天数是基于余额和规则的参考值,不代表通过机器学习模型验证过的预测准确率。

登录会话和网络状态同样影响数据获取。校园系统的接口需要当前登录信息,应用要在界面、请求和本地保存之间处理异常,而不是因为一次失败就丢掉历史数据。桌面组件还有单独的刷新与展示条件,后台任务也受 Android 调度约束。因此,有刷新代码不等于能够保证在每一个指定秒点完成查询。

技术架构与模块组织

应用使用 Kotlin 开发,按照 ViewModel、Repository、Room 数据库和网络服务组织代码。ViewModel 管理界面需要的状态与操作,Repository 协调请求、数据转换和本地保存,Room 通过 Entity、Dao 与 Database 管理记录。Retrofit 和 OkHttp 负责接口访问,Kotlin 协程把查询与存储操作放到合适的线程中,减少等待网络时对页面操作的影响。

界面结合 Jetpack Compose 与 XML 布局,按电费、课表、校园卡等业务划分文件和页面。这样既保留不同页面原有的布局实现,也能逐步组织新的交互组件。模块划分的意义在于把相对独立的业务放在自己的数据结构和状态管理中,例如电费历史不需要与校园卡流水共用一套含义模糊的记录。

后台刷新使用 WorkManager 的 CoroutineWorker,配合电费与课表的 AppWidgetProvider 展示桌面信息。组件提供的是 Android 桌面入口,与应用内完整页面分工:桌面适合快速看当前余额或课程,页面适合查看历史、筛选信息和调整设置。代码也包含深色主题、帮助、隐私说明和更新入口,让应用具备持续使用所需的外围内容。

核心功能与实现

多周期电费分析

ElectricityRepository 分别读取最近 7 天、30 天和 90 天的数据,整理日期与余额,再从相邻记录中区分用电下降与充值上升。消耗记录不仅保留金额,也保留跨越的时间,计算时按有效天数得到日均值。近期数据与长期数据的选择取决于是否有足够记录,而不是把每一种周期都当成同样可靠。

代码对有短期与中期记录的情况进行加权,例如采用近期日均值并参考中期变化;若近期记录不足,则寻找更长期的历史。没有有效消耗记录但存在充值记录时,会尝试分析充值模式;仍缺少依据时再使用估计值回退。数据来源通过枚举与界面状态传递,让使用者能够区分“来自历史”与“暂时估计”。

剩余天数计算还考虑不同阶段的日均消耗变化,并把最终估算限制在 90 天上限。这个上限是应用的保守规则,不是性能指标。它避免少量历史或较低日均值把结果放大成不合理的长期预测。实际消耗仍受人数、季节和用电习惯影响,所以这一功能的用途是提供充值判断参考,而不是替代真实计量。

历史保存与文件导入导出

查询结果保存进本地数据库,变化检测帮助组织电费历史。应用保留时间、余额、楼栋、房间、用电量和充值等字段,后续分析能够围绕这些记录继续进行。历史与当前值分开后,界面可以查看已有数据,也能在网络异常时保留之前的观察依据,而不必把所有内容绑定到一次实时请求。

ElectricityDataExportImport 使用 Android ContentResolver 和文件 URI 读写 JSON、CSV。JSON 导出保留结构化字段,CSV 提供便于表格软件查看的形式;导入时检查必要字段并解析时间与余额。文件入口的意义是让用户能够转移、保存和检查自己的历史,而不是把数据只能留在应用内部。它也让版本维护和用户排错有了更直接的数据依据。

课表导入与桌面展示

课表模块通过 WebView 打开教务登录页面,在用户已有登录状态的基础上注入解析脚本。ScheduleImportActivity 组织页面加载、JavaScript 执行和 Android 回调,接收解析后的课程数组;代码还包含进度提示、学期选择相关适配和备用解析路径,用于应对页面结构及返回内容的差异。

课程进入本地课表数据模型后,由 Repository、Dao 和 ViewModel 管理,再提供到课表页面与桌面组件。这里需要处理的不只是课程名称,还包括教师、教室、星期、周次和节次。如果这些维度在导入阶段混淆,之后的桌面显示再精致也会给出错误时间。因此,解析结果的字段含义与本地存储结构必须一致。

校园卡信息与分页流水

CampusCardRepository 提供卡片信息、月账单、消费趋势和交易列表查询。分页请求把接口返回记录映射到应用内的数据模型,再根据总记录数和页面结果计算页数,将获取的流水保存到 Room。CampusCardViewModel 组织加载状态、月份切换和页面数据,使当前选择与网络结果能够对应,减少界面直接操作接口细节。

流水保存的目的,是让“查到了多少记录”和“当前显示哪一批记录”有清晰依据。遇到账号缺失、响应异常或记录字段不完整时,Repository 返回明确结果,映射过程中也处理无法使用的数据。它没有把一次网络成功简单等同于数据完整,而是把账号、分页、数据转换和保存分别作为需要处理的环节。

后台刷新和组件同步

ElectricityScheduledWorker 在执行时检查当前时间及登录信息,符合条件后获取电费、保存数据、检查变化并更新桌面组件。查询没有获得有效结果时会进入重试或失败返回,避免假装刷新完成。组件刷新与数据库更新放在同一条工作流程里,是为了减少主页面已有新值而桌面仍显示旧值的情况。

不过,WorkManager 的执行时间受系统条件影响,代码中的时间窗口检查还可能使迟到的任务直接跳过。因此,我把这部分写为已有后台刷新实现,而不承诺精确到秒的定时监控。持续维护时需要在不同系统版本、网络和省电条件下检查实际行为,再决定是否调整调度策略和时间窗口。

完成目的与交付内容

YU 已把电费历史分析、课表导入、校园卡查询和桌面展示集中到 Android 应用中,形成从查询到保存、从历史到估算、从页面到组件的完整功能组合。项目完成的目的,是减少日常重复进入多个校园服务的操作,同时把一次性的查询结果变成能够连续使用的数据。

对外展示时,我不使用此前已经删除的日活和准确率数字。源码能够确认数据流程和功能入口,但用户规模、估算误差、后台可靠性以及各机型适配仍需要对应的实际记录。应用内的电费分析也与 Python 工具的占位消耗字段明确区分,避免借一个项目的成熟功能扩大另一个项目的实现范围。

迭代经验与后续方向

这个项目让我同时接触 Android 架构、网页登录适配、网络接口、本地数据库和桌面组件。很多问题出现在它们的连接处:网页登录成功后接口会话是否仍有效,课表脚本返回的字段能否被正确存储,数据库更新后组件是否同步,导入文件中的时间是否可以解析。处理这些问题时,清楚的模块边界比单纯增加功能入口更有帮助。

后续维护重点是登录与校园接口变化、历史数据质量、导入异常和后台调度的实际表现。对电费估算,应持续区分历史来源与规则回退,提供足够信息解释结果;对课表导入,应保留可检查的数据转换过程。继续迭代的方向是让已有功能在日常环境中更稳定,而不是用没有验证过的性能数字包装它。

查看项目源码 ↗

AI 应用与自动化

从文档识别和知识检索,到本地模型与业务工作流。

企业内部应用 · 独立完成 / Python · OCR · RAG

背景

企业内部资料与业务文档需要转化为可检索、可处理的信息,让知识问答与数据处理服务实际工作。

问题

文档内容需要结构化,检索结果需要信息引用,本地模型也需要匹配算力、显存和稳定性要求。

方案

OCR 与企业知识库

  • 独立完成 OCR 识别及结构化处理能力建设,将文档内容用于业务数据辅助处理。
  • 搭建 RAG 企业知识库,支持内部资料检索、智能问答和信息引用。
文档收集汇总内部资料与业务文档
OCR 识别与结构化提取字段并整理为可用数据
知识库检索建立索引,支持语义检索
智能问答与引用回答附带信息来源

技术实现

本地模型部署

  • 独立搭建本地模型环境,完成部署、API 接入及文档分析、知识问答和结构化数据生成应用。
  • 关注模型运行环境、算力、显存、驱动和稳定性,并评估训练、推理及部署的可行性。

数据处理与低代码工作流

基于 Python、TensorFlow / PyTorch 和大模型 API 进行文本、语音与多模态数据处理,使用 Dify、Coze、n8n 构建自动化流程。

  • 将业务需求拆解为高质量 Prompt、输入输出约束和执行步骤,改进对话准确性、格式稳定性及文档 / 表格输出。
  • 开展语音识别、文本分析和图像理解等多模态处理,接入大模型 API 并组织结构化数据流程。
  • 通过 Dify、Coze、n8n 搭建知识库问答、Agent 技能调用、文本生成和表格处理流程。
  • 了解并实践增量预训练、指令微调、强化学习和 Qwen 相关微调,评估模型训练、推理与部署方案。

Prompt 与 AI 工具链

把业务目标、输入资料、输出格式和约束拆成可执行的任务;使用 Claude Code、Cursor、Codex、Trae 辅助代码开发、问题排查与文档整理,结合 Dify、Coze、n8n 组织自动化流程。

最终效果

完成 OCR 文档识别与结构化、RAG 企业知识库和本地模型环境建设,支持内部检索、问答及辅助数据处理。

总结

把业务目标、输入输出约束与模型运行环境一起考虑,并根据使用场景选择本地模型、API 和工作流。

工业视觉与 YOLO 实践

从数据采集、模型训练到本地部署与设备集成。

视觉模型与嵌入式项目实践 / YOLO v5 / v8 · Python · OpenCV

背景

应用场景

围绕工业零件识别、位置定位、缺陷检测、质量检查与机器人视觉开展技术实践,关注复杂现场环境下的模型效果和可部署性。

问题

现场光照变化、遮挡和复杂背景影响图像质量;识别效果、推理速度和设备运行稳定性需要同时验证。

方案

以数据质量为起点,串联采集、清洗、标注、训练和效果验证,再结合现场设备探索本地部署与控制反馈。

技术实现

数据采集与训练

  • 采集工业场景图像,针对光照变化、遮挡和复杂背景开展数据清洗、去重、质量检查、分类及多类别标注。
  • 基于 YOLO 进行多轮迭代训练和效果验证,关注推理速度、误检率及运行稳定性。
图像采集记录工业场景原始数据
清洗与标注去重、分类和多类别标注
YOLO 训练验证多轮迭代,关注误检与速度
本地与嵌入式部署接入设备与机器人控制

部署与机器人集成

  • 开展本地、工业控制器与嵌入式 Linux 部署,探索机器人视觉定位、反馈控制与自动化生产线集成。
  • 将视觉检测结果接入自动化脚本,结合强化学习思路改进执行策略与判断流程;训练并使用独立视觉模型 pt 文件。

最终效果

完成从工业场景数据处理、YOLO 多轮训练验证,到本地与嵌入式部署、机器人视觉集成的项目实践。

总结

模型效果离不开现场数据和部署环境;持续关注误检、速度与稳定性,把识别结果接入实际流程。

AI 数据处理与低代码工作流

基于 Python、TensorFlow / PyTorch 和大模型 API 进行文本、语音与多模态数据处理,使用 Dify、Coze、n8n 构建自动化流程。

项目简介

基于 Python、TensorFlow / PyTorch 和大模型 API 进行文本、语音与多模态数据处理,使用 Dify、Coze、n8n 构建自动化流程。

实践内容

  • 将业务需求拆解为高质量 Prompt、输入输出约束和执行步骤,改进对话准确性、格式稳定性及文档 / 表格输出。
  • 开展语音识别、文本分析和图像理解等多模态处理,接入大模型 API 并组织结构化数据流程。
  • 通过 Dify、Coze、n8n 搭建知识库问答、Agent 技能调用、文本生成和表格处理流程。
  • 了解并实践增量预训练、指令微调、强化学习和 Qwen 相关微调,评估模型训练、推理与部署方案。

技术与工具

Prompt / Agent / Dify / Coze / n8n / TensorFlow

校园业务管理系统

开发校园综合业务管理平台,涉及教务、后勤和设备管理,完成前端交互、业务接口、数据库及服务器部署。

项目简介

开发校园综合业务管理平台,涉及教务、后勤和设备管理,完成前端交互、业务接口、数据库及服务器部署。

实践内容

  • 开展业务需求梳理、前端页面与交互设计,优化操作流程。
  • 参与数据库建模与查询优化,设计 RESTful API 并部署到云服务器,持续维护运行环境。

技术与工具

Vue / React / Spring Boot / MySQL / RESTful API

MCP 语音助手与 IoT 交互

探索把语音对话和设备操作接到同一套工具接口中,通过 MCP 暴露设备查询与控制能力,并组织语音识别、模型回复和语音合成流程。

背景与目标

这个项目探索的是让语音对话具备实际操作入口。用户说出一句话后,系统需要把声音变成文本,让模型生成回复,再把回复转成声音;如果还涉及设备,就需要能够查询设备、读取属性或执行动作。单独接一个聊天接口只能完成其中一段,所以我尝试把模型、语音与设备能力组织在同一套服务中。

项目使用 Python、FastAPI 和 MCP 作为服务与工具入口,通过 WebSocket 和 MQTT 组织连接,设备侧使用 Thing 抽象管理类型、实例、属性和方法。主要源码在 AI_MCP 的 master 分支。由于仓库是私有的,公开案例只描述通用架构和代码实现,不公开配置、凭据或内部资源,也不将它混同为企业 OCR 与 RAG 项目。

对话与设备交互的问题

语音对话是一条包含多个等待点的流程。客户端上传音频后,识别服务可能没有得到文字,模型调用可能失败,语音合成也可能需要较长时间。如果界面只等一个最终结果,用户无法判断系统是否还在工作。因此,会话需要能够返回识别、思考、播报、空闲和错误等状态,并在连接关闭后停止相关任务。

设备操作则需要另一种明确性。服务需要知道可用设备有哪些,设备暴露什么属性,某个动作需要哪些参数。不能把一段自然语言直接变成任意设备命令。因此,设备查询和控制被封装为单独的工具,模型或客户端通过这些已定义的入口进行操作,设备逻辑与连接协议保持相对独立。

技术组织与组件职责

模型、语音和设备分别管理

AIModel 定义加载和生成接口,TransformersModel 负责本地模型,APIModel 负责外部接口,AIModelManager 根据配置选择并管理当前模型。本地分支使用 Transformers 的 AutoTokenizer 和 AutoModelForCausalLM,根据 CUDA 是否可用选择运行设备及精度,并传入生成长度、temperature、top_p 等参数。这说明项目已有本地与 API 两条接入路线,但不代表所有模型都经过部署验证。

ASR 模块通过 speech_recognition 调用 Google Web Speech,将音频转换成文本,识别不了或请求失败时返回空结果。代码预留了其他引擎的扩展方向,但当前不能写成已经完成本地语音识别。TTS 使用独立的引擎与管理模块处理合成请求,和模型生成分开,使替换语音服务时不必重写整条对话流程。

服务入口组织 MCP 工具的动态注册,以及 WebSocket、MQTT 协议的启动与关闭。不同协议接收的请求最后进入统一处理入口,再按原连接方式返回结果。这样,设备能力不必分别为每一种网络连接复制一份,而连接层也不需要了解每一个设备的业务细节。

关键实现与数据流程

独立会话和异步音频队列

StreamProcessor 为每个客户端持有独立的 asyncio.Queue 和处理任务。会话启动时创建后台协程,接收音频块时放入队列,主循环依次累积音频、调用 ASR、取得模型回复,再组织 TTS 输出。过程中通过 WebSocket 返回状态与识别内容,客户端可以区分当前正在处理哪一段。

关闭会话时设置停止状态,并取消已有处理任务,捕获 CancelledError 完成正常清理。这个处理的目的是避免页面离开以后,服务仍继续占用识别、生成和音频输出资源。代码目前采用简化的固定音频累积方式,注释也明确指出需要更完整的 VAD。因此,这是一条已有实现的语音会话链路,尚不能称为成熟的实时语音产品。

统一的设备注册与工具调用

ThingManager 保存设备类型、创建器和已注册实例,创建设备时调用相应创建逻辑,再将实例加入管理器。IoT 工具提供设备列表、单个设备信息、属性查询和方法调用入口。工具函数先找到设备,再进入对应能力,异常时组织可返回的结果,减少连接处理代码直接操作具体设备对象的情况。

这层抽象解决的是扩展方式:新增一种设备时,重点补充它的类型、属性和方法,而不是重新设计所有查询接口。仓库中包含智能灯、虚拟 LED 和系统监视器等设备模块,能够说明接口组织方式。具体物理设备是否连接、控制动作是否生效,仍需要硬件与运行环境中的实际验收。

已完成的目的与当前范围

项目已经将模型接入、语音识别、语音合成、设备管理和工具接口分成可组合的模块,形成语音输入到回复输出,以及设备查询到动作调用的代码路线。它完成的目的,是为“可以交流,也可以调用工具”的助手建立基础结构,让对话与设备操作不再只是两份相互分离的脚本。

公开展示不宣称完整实时性、低延迟指标或物理设备覆盖数量。Google Web Speech 依赖网络服务,本地模型受算力与模型配置限制,WebSocket 中的状态反馈也不等于完整端到端流式推理。已有代码可以核对模块接口与处理顺序,可靠性则需要实际运行和异常场景记录。

后续完善与实践收获

后续工作应围绕语音活动检测、会话取消、网络断开、模型失败以及设备错误继续验收。语音输入到达的速度和处理速度不同,还需要检查队列与超时行为;设备操作也需要明确哪些动作允许执行、哪些结果能够确认。项目让我理解到,模型只是其中一环,服务生命周期、协议和设备抽象决定了助手能否真正接入应用。

OpenWrt 路由管理实践

围绕路由配置、局域网共享与远程任务运行开展实践,并结合多设备项目中的 Docker / procd 适配,探索在支持容器的 OpenWrt 上部署 Worker。

背景与目标

OpenWrt 实践最初来自路由配置、局域网共享和远程管理需求:设备接上网络以后,需要知道是否连通、服务能否访问,以及共享文件和远程任务如何到达正确机器。对我来说,路由器不只是一个上网入口,也可以成为家庭或个人设备环境中的一部分,但应先明确硬件和系统能承担什么。

在有源码可以核对的部分,多设备协作项目提供了 OpenWrt Worker 适配。我把这条部署路线与原有网络实践对应起来,描述如何在支持 Docker 的 OpenWrt 上运行执行服务;不会把它写成独立研发了完整路由监控后台,也不重复计算为另一个原创调度系统。

环境与部署约束

OpenWrt 设备的架构、存储和可用内存不同,不是任意路由器都适合运行 Node.js 与容器。项目中的适配以支持 Docker 的设备为前提,安装脚本检查 Docker 环境,并提供基础镜像配置。连接和运行条件需要实机确认,不能因为仓库有安装脚本就声称全面覆盖各型号。

服务还需要区分允许处理的工作区与保存身份、运行状态的目录。把这些目录混在一起,文件任务就可能接触设备凭据。因此,部署路线把状态卷与工作区分开,配对令牌从标准输入交给安装流程,避免将它直接放到命令参数中。

技术与服务组织

源码使用 Docker 承载 Node.js Worker,通过 procd 管理启动与恢复。镜像没有固定只允许一种平台,OpenWrt 服务脚本配置主机网络、挂载工作区与状态目录,并使用进程恢复参数。Worker 本身继承多设备项目的设备协议和允许目录约束,路由器端主要承担运行环境适配。

这种组织把运行时与系统管理分开:容器提供依赖,procd 负责系统服务生命周期,Worker 处理任务协议。安装脚本将这些步骤连接起来,减少每次重启都手动运行一组命令的需要。实际服务是否稳定恢复,仍需要在对应设备和网络上验收。

已实现的适配与检查

仓库的 OpenWrt 测试核对镜像配置、状态挂载、procd 的 respawn、工作区与状态分离,以及配对令牌输入方式。它们验证的是脚本约束与结构,没有替代真实设备上的安装、连通和执行测试。这个区分让我在描述时能够说明完成了适配代码,同时保留硬件验收边界。

原有路由配置与共享盘经验则用于处理基础连通问题:先判断设备是否在正确网络,服务是否监听,访问路径是否对应,再检查应用逻辑。网络问题不能只从网页报错推断,远程工具也不能默认所有机器之间天然互通。

完成目的与实践范围

这项实践的目的,是把基础网络维护和边缘设备部署连接起来。已有代码提供 OpenWrt Worker 的安装与服务组织路线,原有经历覆盖路由、局域网与文件共享。这里不新增未经实现的实时流量看板、全设备可视化管理或带宽优化功能。

后续方向

后续应针对具体设备检查架构、资源、安装时间、断线与重启恢复,再考虑是否承担更多任务。能够运行容器只是起点,真正的交付还需要知道任务何时成功、失败后如何处理,以及设备资源是否影响原有网络服务。

查看项目源码 ↗

YU · Python 宿舍电费监控

用 Python 定时查询宿舍剩余电量,把查询时间和余额追加到 CSV,再生成趋势图。低余额时在运行终端给出提醒。

背景与目标

宿舍剩余电量通常要进入校园一卡通页面查询,查完以后结果就留在那一次页面里。单次余额能告诉我现在还有多少,却不能说明最近变化得快不快。YU 的 Python 工具从这个简单需求出发:定时查询,把时间和余额留下来,再用图表查看变化,余额较低时在运行终端提醒。

这个项目是独立脚本,与 Android 校园助手分开。Android 版本后来组织了本地数据库、多周期分析和桌面组件;Python 仓库目前关注的是查询、CSV 记录、趋势输出和控制台提示。展示时明确这条边界,是为了让读代码的人能够看到脚本真实完成了哪些工作,而不是把另一个应用的功能一起算进来。

数据获取与提醒的问题

查询依赖校园系统的有效会话。脚本需要用已有登录信息访问接口,响应中还要提取真正的余额文本,不能把 HTTP 请求结束就当成已经取得有效数据。登录信息失效、响应结构变化或无法找到余额,都会影响后续记录。因此,自动轮询首先要确认拿到的是可以使用的数据,再把它追加到历史。

低余额提示也有明确范围。当前实现通过控制台输出提醒,并没有邮件、微信或手机通知链路。如果脚本无人查看地运行,终端提醒不能自动变成能够送达用户的通知。这个限制影响使用方式:脚本适合本地持续观测或作为采集基础,进一步做远程告警还需要增加发送、去重、失败记录与重试机制。

技术选择与脚本结构

项目使用 Python 和 requests.Session 执行请求,pandas 组织表格数据,Matplotlib 生成图像,schedule 按配置间隔触发检查。配置与主流程分开,宿舍查询需要的参数、余额阈值和检查周期不直接散落在每个处理函数中。脚本结构适合一个持续运行的小工具,没有把简单轮询扩成复杂服务。

CSV 是这个阶段的数据存储形式。每次记录带查询时间、余额和消耗字段,能够用表格软件查看,也便于之后转入其他统计程序。对于个人脚本,它比一开始就引入数据库更直接;但 CSV 也没有现成的数据去重、并发写入或事务管理,后续扩展需要根据运行方式选择新的存储方案。

查询、记录与可视化实现

从响应文本提取余额

主流程向校园一卡通查询接口提交请求,余额提取函数使用正则从响应文本中寻找“剩余电量”之后的数值。这个处理让业务值与整段页面或响应内容分离,但匹配规则依赖当前文字和数字格式;如果上游页面改了措辞、格式或返回结构,就需要重新核对提取逻辑,而不是默认每次都能够成功。

获得余额以后,脚本整理为带时间戳的记录,通过 pandas 追加到 CSV。连续数据可以帮助比较不同日期的余额,但当前代码中的 consumption 仍赋值为 0.0,并带有待实现说明。因此,CSV 中存在消耗列不代表已准确计算用电量,也不能从输出图上的这个系列推定真实消耗变化。

用文件输出保留观察结果

绘图函数读取已有记录,通过 Matplotlib 输出余额与消耗相关的趋势图。余额曲线能够展示查询记录的变化,消耗曲线则受到占位字段的限制。图像被保存为文件,适合在本地打开或用于之后的展示接入;仓库本身没有自动发布网站图表,也没有浏览器中的实时刷新页面。

主循环持续运行计划任务,并保留查询与阈值检查的周期配置。这样,同一个采集脚本能够连续执行,而不需要每次手动启动一次查询。调度的意义是减少重复操作,它仍依赖进程正在运行、网络可用和登录状态有效,不能仅凭一个循环就称为全天候可靠监控。

已完成的目的与当前效果

项目已经把人工查询拆成请求、值提取、时间记录、CSV 存储、图像输出和阈值提示几步,为宿舍电量保留连续观测数据。它完成的是一个轻量采集与查看工具,重点在于留下历史,而不是预测或通知平台。脚本的价值是需求小、入口清楚,能够独立运行并逐步补充能力。

当前不展示预测准确率,也不声称已经支持自动推送。消耗量计算、充值识别和邮件或微信提醒仍需要继续实现。与 Android 版本分开展示以后,两个项目各自的职责更清晰:一个是 Python 查询脚本,一个是有本地历史和界面状态的移动应用。

后续完善方向

后续可以先从记录质量入手,处理同一时间重复记录、无效响应和充值变化,再计算具有明确单位与时间跨度的消耗值。若增加通知,需要记录提醒状态,避免余额持续低于阈值时每次查询都重复发送。进程管理与失败日志也是下一步工作,让无人值守运行有清楚的状态可查,而不是只依赖终端仍然开着。

查看项目源码 ↗

AHPU-XiaoAi · 小爱课表适配

适配安徽工程大学教务系统,把课程、教师、教室、周次和节次转换成小爱课表能够接收的数据,省去逐门课录入。

背景与目标

一份教务课表里,课程名称只是最容易看见的部分。它还包含教师、教室、星期、周次和节次,某些课程隔周上课,某些课程在连续几个节次出现。手工录入既费时间,也容易漏掉这些条件。AHPU-XiaoAi 的目标是适配安徽工程大学教务系统,把课程信息转换为小爱课表能够接收的格式。

项目采用 JavaScript,按 provider、parser、timer 三个入口组织。provider 负责在已有网页登录条件下获取课程与学期,parser 负责转换和归并课程数据,timer 提供节次时间。这种拆分让“从哪里拿到课表”“课程如何表达”和“什么时候上课”分别有自己的处理位置,后续调试能够先定位出问题的阶段。

原始课程与目标格式的差异

教务系统的返回结果和小爱课表的课程模型并不完全相同。重复记录、同一节次出现的不同课程、相同课程的多个周次,都需要整理。简单逐条复制会让目标课表产生重复项,也可能把时间冲突覆盖掉。适配器因此先按具体时间展开,处理重复和冲突,再重新归并为便于展示的结构。

课程时间还需要匹配学校作息。上午、下午和晚间的上课安排,以及短课间和大课间,都影响节次开始结束时间。只导入“第几节”而没有正确时间,提醒和展示就会错位。因此,timer 是完整适配的一部分,而不是可以随意沿用另一所学校的默认配置。

技术组织与信息获取

provider 使用浏览器侧 JavaScript、fetch 与表单数据访问教务页面,识别当前登录状态和学期信息,再通过小爱课表提供的交互入口让用户选择学期。随后查询对应课程数据,将后续解析需要的内容交给 parser。它沿用的是用户的网页登录场景,没有建立一个代管所有学生账号的后端服务。

学期选择很重要,因为同一个用户在不同学年、不同学期有不同课程。provider 会获取可用学期并组织选择列表,使导入范围与用户意图对应。页面结构和返回文本仍依赖上游教务系统,适配代码需要随变化维护;能够获取一份课表不等于所有账号、网络路径和年份都已经验收。

课程解析与归并实现

先展开,再处理冲突

parser 将课程展开到具体周次、星期和节次,利用集合去掉完全重复的条目,再按时间整理。时间维度明确之后,同一个时间点是否出现多项课程才容易判断。对于冲突项,代码合并不同课程的信息,而不是默默丢掉后来的记录,保留使用者检查异常的机会。

相同课程的教师与教室信息也进行整理,去除未定义内容,并将不重复的字段归并。之后把相同课程的连续节次重新组合,再合并相同条件下的周次。这个过程把“处理时需要足够细”与“显示时需要足够简洁”分开,避免为了减少条目直接忽略课程时间条件。

课程字段与节次时间

scheduleHtmlParser 接收 provider 传入的数据,将课程名称、房间、教师、星期、周次和节次转换为目标模型。课程名与房间文本还做格式整理,减少原始字符串中的附加标记干扰展示。转换的重点不是把页面文字搬过去,而是让每个字段在目标课表中仍保持相同含义。

timer 根据上课时长、课间长度和休息起点等配置计算节次时间,生成带 section、startTime 和 endTime 的列表。不同时间段分别组织,使上午和下午等作息能够对应。它服务于课程展示与提醒的时间基础,具体配置仍需要与学校最新作息核对,不能只因为脚本可以生成时间就认为时间一定正确。

完成的目的与交付范围

项目已经形成学期获取、课程提取、格式转换、重复与冲突处理、连续节次及周次归并、节次时间生成的适配流程。完成的目的,是减少学生逐门课输入,并尽量保留原课程的时间信息。公开源码能够阅读三个阶段之间的数据转换,方便在导入错误时检查具体环节。

这是一项校园工具适配,不是完整教务平台,也不声称替代学校的排课系统。导入结果仍需要检查,尤其是上游页面变化、特殊课程格式和作息调整。项目没有使用未经测量的导入成功率或用户量包装成果,展示重点是解析路线与实际用途。

维护经验与后续方向

课表适配让我重视中间数据。发生重复、周次错位或教室缺失时,先查看 provider 获得了什么,再检查 parser 的展开与归并,最后确认 timer 的配置,比只盯着最终界面更容易定位问题。后续维护可以补充更多真实课程样例,检查冲突与特殊格式,但样例中应避免公开学生个人信息和登录会话。

查看项目源码 ↗

文件清理管家 · Android 与 Windows

先看清文件占了多少空间、哪些可能重复,再决定是否清理。项目分别实现 Android 应用与 Windows 桌面工具,把扫描、分类、清理规则和回收恢复放在一起。

背景与目标

存储空间不足时,用户往往知道手机或电脑已经很满,却不知道哪些文件占了主要空间,也不确定什么可以删。文件管理器能列目录,但大文件、重复文件、缓存和旧下载散落在不同位置,需要反复查看。文件清理管家的目标,是先把占用整理成能够理解的结果,再让用户决定如何处理。

项目分别实现 Android 应用和 Windows 桌面版 CleanWin。两端都围绕目录扫描、文件分类、空间汇总、清理建议和文件操作组织功能,但没有假装用一套代码覆盖两个文件系统。Android 使用 Kotlin 与平台存储接口,Windows 使用独立 C# 实现。共享的是产品目的和分析思路,各端分别处理自己的权限、路径、界面及回收行为。

扫描与清理面临的问题

扫描结果需要完整,也需要能够被查看。只列几万个路径不能帮助用户做决定,至少需要知道文件类型、数量、大小、位置和修改时间,才能回答“哪里最占空间”。另一方面,文件内容分析比目录遍历更昂贵,重复检测、图片质量和相似图片检查不能不加区分地全量运行,否则一次简单查看也会变成漫长等待。

清理的风险同样具体。相册、聊天应用和 Android 数据目录中,文件看起来可能相似,却未必可以替用户判断是否重要。删除动作还可能部分成功:有些文件已经不存在,有些没有权限移动,有些恢复时遇到同名文件。因此,工具需要保留每次操作的结果,给出风险提示,并提供可以恢复的处理路径。

技术架构与界面状态

Kotlin 扫描器与 ViewModel

Android 端通过 StorageScanner 负责扫描和报告生成,StorageViewModel 管理页面状态,LiveData 将扫描进度、当前筛选、提示和结果提供给界面。扫描与文件操作放在 Kotlin 协程的 IO 环境中,主线程接收状态变化。ViewModel 在开始前检查权限与扫描选项,同时避免已有扫描尚未结束时再次启动。

StorageCache 保留扫描选项和报告,打开应用时能够先加载缓存。ScanIndexDatabase 使用 SQLiteOpenHelper 建立文件表,以路径为主键,保存名称、扩展名、类别、大小及修改时间,并为类别、扩展名、大小和修改时间创建索引。当前 replaceFiles 在事务中清空并重建索引,因此这里是扫描快照的持久化,而不是已经实现增量文件系统监听。

界面围绕分类汇总、文件列表和占用图表组织展示。图表的目的不是给扫描结果加一层装饰,而是先帮助用户找到主要类别,再进入具体文件。筛选状态由 ViewModel 保存,扫描完成后检查原选择在新结果中是否仍存在,避免界面停留在已经没有数据的分类上。

文件分析与操作实现

报告生成和可选分析

StorageScanner 先整理文件并按大小排序,再计算每个类别的文件数与占用。报告进一步组织目录统计、安装包信息、应用清理分组和规则匹配。重复文件、图片质量、硬链接等分析由扫描选项决定是否执行,并通过不同阶段的进度反馈说明当前工作,避免把所有等待都显示成一句“正在扫描”。

图片分析包括相似分组和低分辨率检查,文件侧还包含空目录、残留目录及安装包等入口。这些结果被作为待检查的分组,而不是自动删除指令。不同分析使用不同的数据条件,工具将候选项汇总给用户,最终是否清理仍由用户选择,不能把规则命中直接等同于文件没有价值。

逐步缩小重复检测范围

Windows 扫描器先把文件按大小分组,只有同样大小且不止一个文件的分组继续参与检测。随后计算局部指纹,把候选再次归并,再对仍存在多个成员的分组计算完整哈希。这样的分阶段处理避免所有文件一开始都读取完整内容,将较昂贵的比较集中到更可能重复的候选上。

重复分组还需要与可移除文件列表区分。检测说明内容相同,不说明路径用途相同,因此界面与清理报告保留分组信息供检查。项目没有附带能够证明所有目录规模下的性能数字,我也不把这套筛选路线写成未经测量的速度提升百分比。能够核对的是处理顺序和减少候选的机制。

回收目录与恢复路径

Android 删除先把文件移入应用的回收目录,并记录原路径、文件名、大小、类别和删除时间。操作结束返回已移动与失败列表,索引记录用于后续展示和恢复。恢复时先创建原目录,原路径已经有文件则寻找不冲突的目标名称,避免简单覆盖;另一个入口才负责彻底删除回收记录对应的文件。

这套回收机制使“清理”与“不可恢复删除”成为两种不同操作。ViewModel 根据实际返回结果更新报告,将已移动和已经不存在的路径从当前数据中移除,保留失败说明。文件操作不是一个布尔值就能表达的事情,结果列表能够帮助用户知道这次到底处理了哪些文件。

规则分类与风险提示

StorageSafety 将路径统一为便于比较的形式,对相册、聊天应用和 Android 数据目录等敏感位置生成提示。Windows 的 CleanupRuleLibrary 根据路径关键字、文件名前后缀匹配日志、临时文件、缩略图和旧备份,并记录规则的风险等级。它们提供的是可解释的线索:为什么这个文件被推荐查看,以及删除前需要注意什么。

Windows 和 Android 都保留了本地处理的思路。扫描结果、分类与清理操作围绕当前设备展开,不能因为能识别缓存就声称可以安全清理任何应用数据。平台权限、文件占用和目标目录的实际状态仍会影响结果,工具需要接受部分失败,并把它展示出来。

完成目的与交付内容

项目已经从“列出文件”走到“汇总空间、发现候选、检查风险、执行操作、保留恢复信息”。Android 与 Windows 分别提供可以阅读的源码,扫描、分类、重复分析、清理规则和回收逻辑都有具体实现。它完成的目的,是让存储整理有依据,让用户先看到信息再作决定。

目前不提供虚构的清理容量、设备覆盖率或扫描速度指标。代码也不能代替对不同系统版本、权限条件和真实文件结构的验收。尤其在相册及聊天目录中,规则只是提示,不能替用户判断个人资料是否可以删除。

实践收获与后续方向

这个项目让我把文件系统、异步界面、缓存索引和风险控制放在同一条流程里考虑。扫描时要持续反馈,报告要能够逐层查看,删除后要与当前列表一致,恢复时又要处理路径变化和同名文件。后续可以继续改进大目录体验、增量索引、规则解释与预览,但每一项优化都应保留清楚的操作结果。

查看项目源码 ↗

Multi-Device Coding · 多设备开发协作

让一台电脑上的 AI 开发会话协调自己的其他设备执行任务。把会话状态、远程工作区和执行结果留在自托管服务里,减少跨机器开发时反复搬文件和重新交代上下文。

背景与目标

我希望一次 AI 开发会话能够使用自己已有的多台设备,而不是每换一台电脑都重新复制代码、说明上下文和启动任务。Windows 可以处理自己的构建环境,Linux 服务器可以承担常驻服务,其他机器还可能提供测试或算力。Multi-Device Coding 想把这些设备连接成可协调的执行层,让会话围绕项目继续,而不完全依附某一个终端。

项目使用 TypeScript 和 Node.js,采用自托管 Coordinator、Worker 与 MCP 客户端分离的结构。当前处于 Alpha 阶段,仓库已经包含会话、设备、工作区和执行机制,稳定发布仍需要证明新设备安装、真实多机并发和跨客户端接管。这里的目标与已实现的代码都会分别说明,不把产品方向当成验收结果。

多设备开发的关键问题

分布式执行首先要解决状态属于谁。聊天窗口关闭以后,任务是否还存在,设备是否仍在执行,另一台客户端能否继续项目,都不能只依靠上一轮对话文本。项目因此让常驻 Coordinator 持有项目、会话、事件、检查点和结果,MCP 客户端提供工具入口,Worker 负责在设备上执行,而不是让所有状态留在客户端内存里。

第二个问题是不能让多台设备任意覆盖彼此的工作。任务需要清楚的工作区,执行需要知道使用哪一份源码,回收的产物也需要能证明来自哪一轮操作。连接中断时,如果客户端简单重发命令,还可能重复启动带副作用的任务。因此,工作区隔离、稳定操作标识、恢复与结果校验都进入了项目设计。

技术架构与组件分工

控制平面和设备执行层

control-plane 管理项目、会话、设备和工作区;device-registry 保存设备平台、角色、能力、在线状态与工作区位置;worker-runtime 提供认证连接及实际执行入口。类型定义通过共享 contracts 和 worker-protocol 组织,减少各层对同一字段产生不同理解。MCP 工具再将这些能力暴露给开发客户端。

设备注册信息不只有一个 IP。它还包含平台、架构、能力、角色、最后活动时间和心跳状态。设备选择可以根据所需平台、能力、角色以及在线、空闲条件寻找合适目标。这样,调度才有机会区分“这台机器可以跑”和“这台机器适合这项任务”,也为后续多种设备分工保留入口。

传输支持本地、Worker 通道和 SSH 路由等形式,持久状态与连接方式相对分离。SSH 相关路线保留主机密钥校验;远端 Worker 通过配对与设备凭据建立连接。项目不要求由作者运营中心中继服务,设备和 Coordinator 都由使用者自行部署,可选网络方案也不被写成必要条件。

会话、工作区与执行实现

单写租约与会话接管

Coordinator 暴露会话附加和释放入口,读取持久快照中的租约,判断当前所有者及过期时间。已有其他客户端仍持有有效租约时,不默认静默夺取;释放、等待过期或明确接管是不同处理路线。这个机制针对的是多个开发客户端同时写入同一会话的问题,而不是单纯让所有客户端看到相同聊天记录。

项目、会话、任务事件、检查点和结果共同组成恢复依据。客户端连接恢复后需要先理解当前状态,再决定继续或接管,不能把“重新连上服务器”直接等同于“所有执行都已恢复”。因此,当前 Alpha 的验收中特别保留了跨电脑接管和执行中任务协调,而不只检查进程能够启动。

Git worktree 与不可变快照

task-runtime 为任务建立 Git worktree 与对应分支,使开发和验证可以在隔离位置进行。原始仓库、工作区位置和任务结果之间保留关系,避免几台设备对同一个目录无序修改。工作区隔离既帮助回收改动,也让后续检查能够对应到具体任务和分支,而不是只拿到一份不清楚来源的文件夹。

workspaceBundles 整理源码与产物的文件清单、大小、模式和摘要,创建 Bundle 后校验整体内容,再将快照物化到目标工作区。对未跟踪文件和敏感路径有单独处理,避免把开发者目录中的凭据或无关文件顺手带到另一台设备。这里的目标是明确传输范围,而不是无限制同步整个电脑。

产物校验与可恢复操作

从 Worker 回收远程产物时,代码比较文件大小和 SHA-256,检查收到的内容是否与记录一致。校验能够发现内容损坏或不匹配,却不能证明程序逻辑正确,因此还需要构建、测试或其他业务验收。项目把“传输完整”和“产物可用”作为两个不同问题处理。

Worker 运行时保留 operationId 相关的执行与恢复路线,通过 process.resume 等入口查询已有操作,而不是每次网络重连都重新执行命令。执行结果和过程事件由协议传递,支持取消及终端相关能力。稳定操作标识的目的,是让客户端可以判断自己正在继续原任务,还是准备启动一个新动作。

配对、目录边界与生命周期

配对服务用短期令牌登记设备并发放独立凭据,后续连接再使用设备凭据认证。AllowedRootPolicy 将允许目录解析为真实路径,检查访问目标与允许根目录的关系;写入新路径时还检查已有父路径,减少通过路径变化越过工作区边界的情况。这些代码界定执行层能接触哪些文件,不能把远端 Worker 当作不受限制的远程 Shell。

不同部署环境还有各自的进程管理。OpenWrt 路线使用 Docker 和 procd,拆分工作区与状态卷,配置服务恢复;桌面与服务器提供对应安装入口。仓库中的 OpenWrt 测试检查镜像、挂载、配对令牌输入和服务脚本约束,但测试文件的存在不表示每种路由器硬件都已完成实机验收。

已完成的目的与阶段结果

项目已经形成从客户端工具入口,到常驻状态管理,再到远端设备执行、工作区物化和产物回收的代码结构。它完成的基础目的,是把多设备开发需要的状态、身份和工作区概念组织到同一层中,减少开发过程依靠临时 SSH 命令与手动搬文件连接的情况。

当前仍按 Alpha 展示。新设备引导安装、至少两台真实 Worker 完成依赖任务以及另一台客户端无损接管,需要对应实测证据。项目中的任务图、恢复与能力选择接口不能直接写成已经长期稳定运行的集群调度成果,也不提供未经测量的并发或效率数字。

工程收获与后续方向

这个项目的重点逐渐从“远程执行一条命令”转向“知道执行的是哪项任务,使用哪份代码,结果在哪里,失败后如何继续”。下一步完善应围绕安装可靠性、断线恢复、任务依赖和结果回收进行真实多设备验收。架构继续扩展时也要保持状态归属清晰,让客户端、传输和 Worker 可以变化,而项目记录能够连续保留下来。

查看项目源码 ↗

校园卡流水导出与消费分析

从校园卡网页查询个人交易记录,导出到本地,再在浏览器里查看消费结构和余额变化。把分散的流水整理成可以自己检查的数据。

背景与目标

校园卡交易页面适合查看最近几笔记录,却不方便回顾较长时间的消费。连续翻页以后,仍很难知道哪个月花得多、主要在哪些商户消费,或者余额在什么时候发生变化。这个项目把查询与分析分成两段:先通过桌面工具取得个人流水并保存,再用网页查看数据的分布。

实现使用 Python、PySide6 和浏览器页面。桌面端完成登录、查询进度与文件导出;分析端使用 JavaScript 和 Chart.js,把本地 JSON 组织成汇总与图表。这样,已经导出的数据可以反复查看,不必每次打开分析页都重新登录校园服务,也不需要把个人交易上传到一个新的分析服务器。

查询与分析需要解决的问题

流水通常是分页返回的。查询工具需要知道开始与结束页、当前获得多少记录、是否还在继续,以及中途失败或用户停止时如何反馈。若把所有网络请求都放在界面主线程,用户很难知道进程是在等待还是已经卡住。因此,查询过程需要与界面更新分开,并让每一批数据与当前进度同步。

交易记录也不能只按金额排序后直接解释。收入与支出、交易类型、商户、日期和余额分别回答不同问题。分析页面需要先整理有效记录,再按月份、星期和时段等维度聚合;分类规则只是查看数据的一种方式,并不能替用户判断每笔消费的真实用途。

桌面工具与网页分析架构

PySide6 界面内嵌校园卡网页,让用户自己完成登录,再读取当前会话用于查询。获取任务放在 QThread 中,通过 Signal 将页码进度、记录批次和完成状态传回界面。开始查询、停止查询、保存数据使用不同入口,避免网络函数直接承担按钮状态与提示文字等界面职责。

文件导出提供 CSV 与 JSON。CSV 遍历记录字段组织表头,方便在表格软件查看;JSON 保留结构,适合后续网页读取。分析页通过 FileReader 读取用户选中的本地 JSON,并在浏览器中执行解析和聚合。这里的“本地读取”说明交易数据的处理位置,不代表页面引用的第三方图表资源全部离线可用。

分页获取与图表组织

后台分页与进度反馈

获取线程按设置的分页范围调用查询函数,检查响应中的 rows,累积记录并逐批发送给界面。根据 total 和当前返回记录数估算总页数,在页与页之间保留短暂停顿,减少连续请求。stop 修改运行标志,循环在继续下一页前检查,给用户提供中途结束的控制入口。

程序同时区分一次请求失败与全部完成。失败时返回具体页码相关说明,完成时汇总取得的记录数。这个机制让用户知道哪些部分已经拿到,哪些部分可能需要重新查询。不过,分页循环中的边界条件仍需要运行验收,不能只凭“支持所有页”的界面选项就保证任意记录量都能完整下载。

数据处理与不同图表维度

网页先解析 JSON,再由 processData 组织交易,生成整体统计和月份数据。汇总卡展示金额和交易数等概要,月份选择用于切换对应图表。商户消费排行、交易类型、消费类别、星期和小时分布分别提供不同观察角度,余额趋势则保留时间变化,避免所有问题都挤进同一张图。

页面也对商户设置类别映射,用于观察预设消费类别。这样的分类便于读图,但规则可能无法覆盖所有商户,需要保留原交易信息供核对。切换或重建图表时先销毁旧实例,避免同一画布反复叠加;错误区域负责显示文件解析和图表数据问题,帮助区分没有数据与处理失败。

已完成的目的与使用范围

项目已经连接登录查询、后台分页、文件导出和浏览器可视化,为个人校园卡记录提供了一条可以保存和反复分析的路线。它完成的是“把流水拿出来,再看清消费结构”,而不是财务管理平台,也不尝试替用户自动判断合理消费或输出信用评价。

代码可以核对查询线程、导出函数和图表聚合入口,但完整记录、不同数据格式和页面适配仍需要真实运行验证。公开作品只展示通用功能与技术,不展示个人流水、会话 Cookie 或账号信息。数据文件属于使用者自己的记录,不作为网站的演示内容公开。

实践收获与后续方向

这个项目让我把数据采集和数据解释分开考虑。获取阶段关心是否完整、是否失败、能否停止;展示阶段关心分类是否清楚、月份是否对应、金额方向是否正确。后续可以补充分页边界、字段变化和重复记录处理的验证,并改进类别规则说明,让每一个图表都能回到它所依据的记录。

查看项目源码 ↗

照片 GPS 提取与地图展示

从带定位信息的照片中提取拍摄时间和坐标,生成可以在浏览器打开的地图,让照片的位置从文件属性中走出来。

背景与目标

照片文件里常常保存了拍摄时间、设备和 GPS,但这些信息藏在属性里,一次只能看一张。照片 GPS 提取与地图展示项目想把这组信息拿出来:扫描一个目录,整理带定位的照片,再生成一份能在浏览器打开的地图,让照片与位置建立直观联系。

项目使用 Python、Pillow、pandas 和 Folium。Pillow 读取照片 EXIF,pandas 保存和转换中间表格,Folium 组织地图标记与路径图层。输出形式是 HTML,适合离线保存文件后在浏览器查看,但地图底图是否能够加载仍取决于使用的资源和网络条件,不能把一个 HTML 文件直接等同于完整离线地图。

文件信息与地图数据之间的差异

不是所有图片都包含定位信息。文件可能没有 EXIF,格式可能无法读取,GPS 字段也可能缺失。因此,脚本需要逐个处理并跳过没有可用信息的文件,不能把目录里的每张图片都假定为一个有效坐标点。拍摄时间、设备型号和定位信息也要与路径一起保留,方便核对数据来自哪张照片。

GPS 在 EXIF 中以度、分、秒等结构保存,而地图需要十进制度。转换过程中还涉及南北纬与东西经方向,连接多个点时又涉及排序。当前代码完成了基础换算和连线,但没有完整处理半球方向,也没有先按拍摄时间排序。因此,它是一项照片定位展示实验,不是已经完成通用轨迹重建的地理系统。

技术组织与处理中间文件

入口沿着三个函数阶段执行:process_photos_and_save_to_csv 整理照片信息,extract_gps_coordinates_and_save_to_csv 转换坐标,visualize_gps_coordinates_on_map 生成地图。每一步通过 CSV 衔接,中间结果能够分别查看,有助于判断问题来自图片读取、坐标转换还是地图生成。

目录扫描使用 os.walk,覆盖子目录中的常见图片后缀。Pillow 打开文件并读取 EXIF,通过 TAGS 和 GPSTAGS 把字段标识转换成可读名称。带 GPS 的照片被组织为文件路径、拍摄时间、设备品牌、型号和原始 GPS 信息。读取失败时记录说明并继续处理其他图片,而不是让单个坏文件直接终止整个目录。

坐标提取与可视化实现

从 EXIF 到十进制度

坐标转换阶段读取 CSV,对保存的 GPS 结构使用 ast.literal_eval 解析,而不是直接执行字符串。随后提取纬度与经度的度分秒,按“度 + 分 / 60 + 秒 / 3600”计算十进制度,保留其他照片字段并输出新的表格。当前代码仍需要补足方向参照和部分 EXIF 类型的兼容,不能默认所有设备数据都符合一种结构。

CSV 中间形式降低了函数之间的耦合,也暴露了格式转换的成本。原始数值结构经过字符串与表格再解析,遇到特殊数据类型时可能失败。因此,后续可以改为更直接的结构化传递,或者明确规范写入形式;当前作品展示的是已有管线,不把这种改进方向写成已经实现。

标记、弹窗和路径图层

Folium 创建地图,将有效记录转换为 Marker,弹窗展示纬度、经度和拍摄时间。相邻有效点之间加入 PolyLine,并放入默认隐藏的 FeatureGroup,用户通过 LayerControl 决定是否显示路径。标记回答“照片在哪里”,路径提供连续查看的辅助,两种信息不强制同时显示。

路径沿输入记录的处理顺序连接,因此同一条线可能反映目录遍历顺序,而不是旅行顺序。拍摄时间格式异常也可能影响展示。这些限制需要在使用时明确,不能将画出来的折线当作经过时间校准的真实行程。当前代码还会在主流程结束后删除中间 CSV,若要保留排查依据,需要调整这一行为。

完成目的与当前结果

项目完成了从照片目录提取信息、转换坐标到导出交互地图的基础路线,让原本藏在图片属性中的位置能够集中查看。源码里可以核对异常跳过、字段整理、标记弹窗和可开关路径图层,技术选择围绕一个明确的小需求组织,没有扩成无关的平台功能。

当前没有实现完整的方向转换、时间排序、轨迹纠偏或照片缩略图浏览,也不提供定位准确率数字。照片中保存的位置取决于原始 EXIF,脚本只是整理与展示这些信息。公开作品不展示个人照片和坐标样本,避免把真实活动位置当作演示数据。

后续方向与实践收获

后续应优先补足 GPS 方向和数值类型处理,按有效拍摄时间排序,并明确缺失或错误时间如何参与路径。再考虑调整地图初始视野、保留中间数据和添加可核对的来源入口。这个项目让我注意到,能生成一张地图和能正确解释一组地理数据是两件事,数据含义需要和可视化一起处理。

查看项目源码 ↗

网页全景展示实验

用一张全景图片在浏览器里建立可以转动查看的场景,探索普通网页中更直观的空间展示方式。

背景与目标

一张平面照片只展示一个方向,全景图片则可以让观看者自己转动视角。我做这个网页实验,是想把全景资源放进普通浏览器页面,用一个直接的交互入口展示空间,而不要求访问者安装专门的查看软件。它适合校园介绍、场景展示或个人页面中的小型视觉体验。

项目通过 JavaScript 集成 Three.js 和 Panolens,页面以一个铺满视口的容器承载全景。这里使用的是现有图形库提供的场景与交互能力,我完成资源加载、容器和参数配置,不把第三方引擎写成自己研发的 3D 框架。仓库名包含 tween,但本案例的主要实现入口是全景页面,不声称原创补间动画引擎。

图片资源与页面交互的问题

全景展示首先需要合适的图片资源。普通照片并不会因为放进全景查看器就具有完整空间信息,输入图片的投影和尺寸会影响最终效果。页面容器也需要明确宽高,否则库已经初始化,用户却可能看不到正确画面。因此,实验先把资源路径、脚本加载和容器尺寸这几件基础事情连接起来。

自动旋转提供初次进入时的观察方向,但过快会让人难以看清,立即启动也可能干扰主动操作。页面因而配置旋转速度与激活等待时间,让图像在展示与查看之间保持基本节奏。实际观看舒适度仍需要浏览器与设备上检查,不能从一个配置值直接推定所有用户的体验。

技术选择与场景组织

HTML 引入 three.min.js 和 panolens.min.js,CSS 将 body 和容器设置为完整视口。JavaScript 通过 PANOLENS.Viewer 指定 container,再创建 PANOLENS.ImagePanorama 并传入图片路径,最后将全景加入 Viewer。图形库负责底层渲染与查看交互,页面负责把素材和展示环境交给它们。

这种实现适合验证一张全景能否进入网站,结构短、入口直接。它尚未引入组件框架或复杂资源管理,没有把基础展示包装成多个场景的内容平台。独立页面也便于检查脚本路径与图片是否能够加载,之后再决定如何嵌入其他网页。

已实现的界面与动画配置

Viewer 配置了自动旋转、启动等待和旋转速度,同时指定全屏、设置等控件入口。用户可以进入全屏查看,自动旋转则帮助展示画面不同方向。项目没有自行实现这些控件背后的渲染算法,展示重点是对已有能力的选择与接入。

图片通过相对资源路径加载,页面添加全景对象后由查看器呈现。当前只有一个 ImagePanorama,没有实现多场景跳转、热点导航、资源预加载进度或错误重试界面。把这些未实现内容留在后续方向中,能够让读者区分一个可运行的基础示例和完整全景导览产品。

完成目的与当前范围

项目已经完成浏览器全景展示的基本闭环:加载库、建立容器、接入图片、配置观看控件和自动旋转。它达到的目的是探索网页中的空间展示形式,为后来在网站中使用图形与交互提供实践基础,不属于生产级数字孪生或精细三维建模项目。

已有代码说明展示路线,但图片质量、加载耗时、移动端手势、GPU 兼容及动画舒适度还需要实际检查。因此不使用帧率、模型精度或设备覆盖率数字,也不把一张全景图片描述成带几何和物理结构的 3D 模型。

后续方向

如果继续完善,我会先处理资源加载状态、失败提示和不同屏幕尺寸,再考虑多个场景之间的切换与热点说明。自动旋转还应尊重用户操作和减少动画偏好。这个实验的收获是明确素材、图形库和网页交互各自的职责,让视觉效果为内容服务,而不是只让页面一直转动。

查看项目源码 ↗

校园网站与页面设计练习

围绕校园介绍和信息展示制作多页面网站,练习导航、图文编排、图片浏览与页面交互,并整理到同一个作品仓库。

背景与目标

校园介绍页面有很多需要同时放进去的信息:学校概况、照片、不同栏目与相关入口。我的网页设计练习围绕这些内容展开,尝试把信息组织成能够浏览的多页面网站。目的首先是练习导航和图文编排,让访问者知道当前看什么、下一步可以去哪里,而不是只做一张静态截图。

Web-Design- 的主要分支是 master,仓库保存了不同主题的页面与资源;Web-devise 也保存了校园导航和全景等关联练习。它们在这里作为前端学习作品展示,不与既有校园业务管理后台合并。页面和组件集成能够被源码确认,但不能由此推定存在 Spring Boot、MySQL 或完整教务管理业务。

页面需要解决的信息问题

图片多的页面容易出现一个问题:轮播、导航和图文模块都有视觉效果,真正的内容却被挤在后面。练习中需要先确定首页与栏目的关系,让主要信息有顺序,再选择组件承载图片和交互。导航应该帮助到达内容,而不是只作为页头装饰;移动屏幕上也需要能够展开和关闭。

另一个问题是第三方组件与自己的内容如何连接。现有轮播、灯箱和滚动脚本能够减少基础交互的编写量,但页面仍需要匹配它们的 HTML 结构、样式和资源路径。组件加载成功不代表内容组织正确,因此调试要同时看脚本行为、布局和实际入口。

技术与资源组织

页面采用 HTML、CSS、JavaScript 和 jQuery。主题目录分别保存入口、样式、图片与脚本,主页面通过相对路径引用资源。校园页面集成 responsive slides、Owl Carousel、lightbox 和 SmoothScroll 等现有组件,提供轮播、图片浏览和滚动交互。作品中的第三方库与模板能力属于上游,我的展示重点是页面内容、资源和交互的组合。

这种目录结构适合学习完整网页如何从一个入口连到多个静态资源。它没有引入 SPA 状态管理或服务端数据接口,更新内容主要依靠编辑页面与资源。技术选择与项目规模对应,不能因为页面看起来丰富就把它描述成已有完整后台的内容管理系统。

导航、图片与页面交互

小屏导航与栏目入口

页面通过 navicon 的点击事件切换菜单状态,使导航在较小屏幕上有独立的开合入口。多页面链接组织不同栏目,页面内链接连接相应内容区块。实现目的,是让相同内容在桌面与窄屏都能找到,不需要把所有导航一直展开占据画面。

导航还有链接正确性和当前页面识别的问题,需要结合实际页面检查。仓库中的组件初始化只能确认有菜单与跳转代码,不能代替所有屏幕宽度下的可用性验证。因此,作品说明保留“页面设计练习”的范围,不使用经过全面适配的笼统说法。

轮播、灯箱与滚动

首页轮播与图片展示区通过既有插件初始化,灯箱用于在当前浏览流程中查看图片,平滑滚动用于连接页面区块。它们分别承担不同作用:轮播呈现内容片段,灯箱查看细节,滚动帮助进入正文。交互配置围绕这些目的设置,而不是所有模块都叠加同样动画。

页面也需要正确引用图片与脚本,避免本地目录调整后资源失效。关联仓库中的全景页面尝试了另一种图片展示方式,但仍属于图形库的集成实验。它们让前端练习从静态排版延伸到浏览行为,同时没有把已有插件的算法算作原创代码。

完成目的与作品范围

这些练习完成了校园主题内容、页面导航、图文布局与组件交互的组织,积累了前端文件结构、资源管理和浏览器调试经验。作品可以通过仓库检查具体页面和初始化代码,说明我如何把已有素材与组件组合成可浏览的页面。

目前不展示访问量或加载性能数字,也不声称建立完整校园管理系统。部分页面使用模板与第三方资源,因此对外描述明确采用“设计与集成练习”,避免把整个页面生态中的通用能力都归为个人原创。它的价值在于实现和理解完整页面,而不是以功能数量代替技术深度。

后续改进方向

继续完善时,可以先统一语义结构、资源引用与小屏布局,再检查图片体积、键盘操作和减少动画偏好。对于轮播和灯箱,应保证正文仍能独立阅读,主要入口不依赖动画才能发现。这个项目让我认识到,网页效果要和信息层次一起设计,组件多并不自动意味着页面更好用。

查看项目源码 ↗

YOLOv5 游戏画面识别与数据集处理

以游戏画面为对象整理检测数据,完成视频抽帧、标注格式转换和训练集划分。通过一个具体场景练习视觉数据准备与训练迭代。

背景与目标

这个视觉项目以游戏画面为对象,练习从原始视频到检测数据集的准备过程。图像检测需要的不只是一个训练命令,还需要能够持续获得图片、把标注转换成统一格式,并明确哪些数据用来训练、哪些用来验证。我围绕这些环节编写脚本,整理游戏中的角色、场景对象和界面元素。

主要源码在 yolov5-diwurenge 的 master 分支,main 只保留了简短说明。项目使用 Python、OpenCV、Pascal VOC XML、YOLO 标签格式和 scikit-learn,仓库中还有多轮训练目录与配置。本案例明确属于游戏画面实践,不与工业零件、缺陷检测和机器人项目混为同一份证据。

数据准备需要解决的问题

视频是连续内容,逐帧保存会产生大量相近图片,完全手动截图又很难控制间隔。因此,抽帧需要依据视频帧率,把时间间隔换算为帧数,再给图片统一编号。不同视频还需要检查是否能够打开、帧率是否有效,否则后续取模和采样可能出现错误。

标注工具产生的 XML 与 YOLO 训练所需文本不同。类别名需要映射为类别序号,框坐标需要按图片宽高归一化;训练和验证还必须同时保留图片与对应标签。若图片已经划分,标签却没有同步复制,数据集表面完整,训练时仍可能缺少监督信息。

工具选择与处理链路

cut.py 使用 OpenCV 的 VideoCapture 批量读取视频,按帧率和设定秒数计算采样间隔,再调用 imwrite 保存图像。xml-txt.py 使用 Python ElementTree 解析 VOC 文件,将 object、类别和边界框转换成 YOLO 文本。split.py 使用 train_test_split 按固定随机种子划分图片,再通过 shutil 将图片与标签复制到对应目录。

这三个脚本把采集、格式转换和数据划分分开,便于分别检查输入与输出。代码中的一些目录仍是特定机器上的路径,因此这里展示的是个人数据准备流程,不把它描述成无需配置即可处理所有数据的通用训练平台。迁移到其他设备时,需要先整理路径和数据说明。

具体实现与数据约束

按时间间隔抽帧

视频抽帧先取得 FPS,再将配置的秒数转换为 interval_frames。读取每一帧时检查帧序号,达到间隔就保存,并使用跨视频连续计数生成名称。代码检查视频是否打开和 FPS 是否为零,完成后释放 VideoCapture,保留处理数量与时间的控制台信息。

这种采样便于得到间隔一致的候选图片,但不保证图片内容足够多样。连续游戏画面仍可能重复,类别也可能分布不均。后续还需要查看样本内容、去重和补足稀少目标,不能只用抽出的图片总数判断训练数据质量。

VOC 到 YOLO 的标注转换

转换脚本读取图片尺寸和每个 object 的类别、xmin、ymin、xmax、ymax,计算框中心与宽高,再分别除以图片尺寸,输出类别序号和归一化坐标。未知类别输出提示并跳过,避免把不在类别表中的名称强行映射到错误编号。类别表的顺序也需要与训练配置保持一致。

这个过程体现了数据格式转换中最重要的约束:同一个目标在不同文件里应该有同样的含义。图片尺寸缺失、框越界或类别不一致都需要进一步检查,当前脚本没有替代完整数据集质量验收。因此不把标签能够写成文本等同于标注已经完全正确。

训练与验证目录划分

split.py 按 80% 与 20% 的比例划分训练和验证图片,设置 random_state 使相同输入下的结果更容易复现。复制前检查标签是否存在,没有对应文本就提示并跳过,图片与标签分别进入 images、labels 的 train 和 val 目录。这保证目录布局与训练使用方式一致。

不过,当前随机划分以图片为单位。来自同一段视频的相似帧可能进入训练和验证两侧,影响对泛化能力的判断。更严格的后续验收需要考虑按视频或场景划分。仓库的多轮训练记录可以说明进行了迭代,但没有重新执行训练和检查完整结果时,不据此写出准确率或实时推理性能。

完成的目的与阶段结果

项目完成了视频抽帧、标注格式转换和训练验证目录整理,形成能够继续训练的基础数据路线。它帮助我把视觉项目从“使用一个现成模型”延伸到数据准备和迭代组织,理解模型效果与样本质量、类别定义和验证方式之间的关系。

当前没有将游戏识别结果直接作为工业视觉成果,也不声称已经完成自动化游戏控制、强化学习策略或现场设备部署。已有脚本与训练目录是可以核对的交付,其他应用能力需要独立实现和证据,不能从仓库名称或目标类别推定。

后续方向与实践收获

后续应补充路径参数化、标注校验、重复样本处理和按场景划分的验证,再讨论模型与部署优化。对视觉实践来说,数据是否具有代表性、标签是否一致、验证是否存在泄漏,比简单增加训练轮次更值得先检查。这也是本项目留下的主要工程经验。

查看项目源码 ↗

编辑评论