一、项目整体流程
scss
原始图片(src/) ──▶ [pose_classifier.py] ──▶ 按姿态分割的人物(output/)
│
▼
[desktop_pet.py] ──▶ 桌面宠物窗口
步骤一: pose_classifier.py 读取 src/ 中的大图,将图中 6 行人物按行切分,每行内部再按投影法切分出单个人物,存入 output/ 下按姿态命名的 6 个文件夹。
步骤二: desktop_pet.py 加载 output/ 中的所有人物图,以无边框、置顶、透明背景的方式显示在桌面上,每个姿态内的 5 张图循环播放形成帧动画,点击切换姿态。
二、核心文件说明
1. pose_classifier.py --- 图片分割脚本
作用: 将源图中的多行人物按姿态逐一切分并归类。
关键技术:投影法(Projection)检测行列边界。
ini
# 将图像沿垂直方向投影,统计每列的"内容密度"
gray = cv2.cvtColor(row_img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 245, 255, cv2.THRESH_BINARY_INV)
v_proj = np.mean(binary > 0, axis=0) # 每列非白色像素的比例
# 找到内容密度低于阈值的位置作为列间空隙
is_gap = v_proj < 0.03 # 3% 以下视为空白列
原理:将每行图像转为二值图后,沿垂直方向统计每一列像素中有内容(非白色)的比例。列与列之间的人物间隙处内容密度接近 0,由此定位切割线。
姿态与行的对应关系:
ini
ROW_POSES = [
"睡觉状态", # 第1行(最上)
"跑步状态", # 第2行
"思考发呆状态", # 第3行
"等待站立状态", # 第4行
"开心卖萌状态", # 第5行
"摸鱼慵懒状态", # 第6行(最下)
]
NUM_ROWS = 6
将图片高度均分 6 份,每份对应一个姿态,再在行内通过投影法检测列间隙完成单个人物裁剪。
2. desktop_pet.py --- 桌面宠物主程序
作用: 加载分割好的人物图,在桌面上显示为可交互的桌宠。
2.1 图片加载与背景透明化(Flood Fill 算法)
scss
def _make_transparent(self, qimg, threshold=730):
"""从图片四边出发,将与边缘连通且接近白色的像素设为透明"""
# 收集所有边缘的"近白色"像素作为起点
for x in range(w):
for y in [0, h - 1]: # 上下边缘
c = qimg.pixelColor(x, y)
if c.red() + c.green() + c.blue() >= threshold:
stack.append((x, y))
# 四方向 Flood Fill:从起点向相邻像素蔓延
while stack:
x, y = stack.pop()
if (x, y) in visited:
continue
visited.add((x, y))
qimg.setPixelColor(x, y, QColor(0, 0, 0, 0)) # 设为透明
for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]: # 四方向
nx, ny = x + dx, y + dy
if 0 <= nx < w and 0 <= ny < h:
c = qimg.pixelColor(nx, ny)
if c.red() + c.green() + c.blue() >= threshold:
stack.append((nx, ny))
原理:类似 Photoshop 的"魔棒+连续"功能,从图片四边开始,只去除与边缘物理连通 的背景白色,人物身体内部的高光/浅色皮肤不会受影响。
>=730(R+G+B)意味着只有接近纯白(255×3=765)的像素才会被纳入透明化。
2.2 窗口属性设置(无边框 + 置顶 + 透明)
python
def _setup_window(self):
self.setWindowFlags(
Qt.FramelessWindowHint # 无边框
| Qt.WindowStaysOnTopHint # 始终置顶
| Qt.Tool # 不显示在任务栏
)
self.setAttribute(Qt.WA_TranslucentBackground) # 透明背景
FramelessWindowHint:去掉窗口标题栏和边框WindowStaysOnTopHint:窗口始终在所有窗口之上Tool:不在任务栏中显示图标WA_TranslucentBackground:允许窗口背景透明,配合透明图片实现"浮在桌面上"的效果
2.3 帧动画循环
python
ANIM_SPEED_MS = 200 # 每 200ms 切换一帧
def _advance_frame(self):
name = list(self.frames.keys())[self.pose_idx]
fl = self.frames[name]
if fl:
self.frame_idx = (self.frame_idx + 1) % len(fl) # 循环到下一帧
self.update() # 触发重绘
原理:
QTimer每 200ms 触发一次,将帧索引 +1 后取模(% len(fl)),实现循环轮播。每次调用self.update()触发paintEvent重绘当前帧。
2.4 点击切换姿态(区分点击与拖拽)
python
def mouseReleaseEvent(self, e):
if e.button() == Qt.LeftButton and self.drag_offset is not None:
delta = e.globalPos() - self.drag_offset - self.frameGeometry().topLeft()
if abs(delta.x()) < 5 and abs(delta.y()) < 5: # 移动 < 5px 视为点击
self._switch_pose()
self.drag_offset = None
原理:记录鼠标按下和释放时的全局坐标差,如果位移小于 5 像素则判定为"点击",否则为"拖拽移动"。这样同一个左键同时支持点击切换和拖拽移动。
2.5 右键菜单
python
def _menu(self, pos):
menu = QMenu(self)
for i, name in enumerate(POSE_ORDER):
prefix = "● " if i == self.pose_idx else " "
act = menu.addAction(f"{prefix}{name}")
act.setData(i) # 将姿态索引存入 action
exit_act = menu.addAction("退出")
exit_act.triggered.connect(self.close)
chosen = menu.exec_(self.mapToGlobal(pos))
if chosen and chosen.data() is not None:
self.pose_idx = chosen.data() # 直接跳到选中的姿态
原理:
setData(i)将每个菜单项与姿态索引绑定,选中后直接跳转;当前姿态前显示●标记。
3. .bat 文件 --- Windows 批处理脚本
| 文件 | 作用 |
|---|---|
run_py.bat |
运行 pose_classifier.py 分割图片,输出重定向到 result.txt |
run_pet.bat |
用 pyw 启动桌宠(start "" 让 cmd 窗口立即关闭) |
.bat 是什么: Windows 批处理文件,本质是一个包含多条 CMD 命令的文本文件。双击后系统逐行执行其中的命令。
less
@echo off :: 不显示命令本身
cd /d D:\MyworkSpace_Java\zhuo-chong :: 切换到项目目录
start "" pyw desktop_pet.py :: 用 pyw 后台启动桌宠,start "" 使 cmd 不等待
pyw vs py: py 是 Python 启动器(带控制台窗口),pyw 是 Python Windows 启动器(无 控制台窗口)。用 pyw 运行 GUI 程序不会弹出黑框。
4. run_pet.vbs --- VBScript 无痕启动器
vbscript
CreateObject("WScript.Shell").Run "pyw D:\MyworkSpace_Java\zhuo-chong\desktop_pet.py", 0, False
.vbs 是什么: VBScript 脚本文件,由 Windows Script Host 引擎解释执行,无需额外安装。
参数详解:
| 参数 | 值 | 含义 |
|---|---|---|
| 命令 | "pyw ...desktop_pet.py" |
要执行的程序 |
0 |
隐藏窗口 | 不在屏幕上显示任何窗口(连一闪而过的黑框都没有) |
False |
不等待 | 启动后立即返回,不阻塞调用者 |
.bat方案即使使用pyw,双击时仍会短暂闪现一个 CMD 窗口。.vbs通过WScript.Shell.Run的第二个参数0完全隐藏启动过程,实现真正静默启动。
三、项目目录结构
bash
zhuo-chong/
├── src/ # 原始图片
│ └── Gemini_Generated_Image_...png
├── output/ # 分割后的人物
│ ├── 睡觉状态/ (5 张)
│ ├── 跑步状态/ (5 张)
│ ├── 思考发呆状态/ (5 张)
│ ├── 等待站立状态/ (5 张)
│ ├── 开心卖萌状态/ (5 张)
│ └── 摸鱼慵懒状态/ (5 张)
├── pose_classifier.py # 图片分割脚本
├── desktop_pet.py # 桌宠主程序
├── run_py.bat # 运行分割脚本
├── run_pet.bat # 启动桌宠(有短暂黑框)
└── run_pet.vbs # 启动桌宠(完全静默,推荐)
四、项目反思(2026-05-17)
开发过程中遇到的问题
电脑缺少 Python 安装包,导致 opencode 无法运行 Python 文件。安装 Python 后 pip 仍无法使用,自己一时没解决。最终让 opencode 自己尝试排查,结果它自动发现并修复了问题,这让我更深入地体会了 opencode 的能力边界和正确使用方式。
项目是否继续?------ 不继续
两个原因:
- Codex 自带的桌宠功能已经足够完善(如实时提醒 coding 进度),再做就是重复造轮子
- 桌宠动作不够连贯,根本原因是 AI 生成的图片质量有限,而我的提示词又是用 AI 生成的------形成了"AI 写提示词 → AI 生成图"的短板叠加效应
怎么让AI生成的图片更好、更稳定。
如何更好地总结项目(除了优化提示词,还有别的方法吗?比如好用的skill?)
目前的做法是直接让 opencode 总结项目,提示词如下:
现在这个项目基本上完成了,帮我总结一下这个项目是怎么实现功能的(还要解释核心文件是做什么的,比如 .bat 是什么文件,有什么用,.vbs 又是什么文件,有什么用,是怎么实现对应的功能的等),核心功能部分需要附加部分代码,并解释代码。
后续可以继续优化提示词,让总结更结构化,比如要求按功能模块分节、增加流程图等