2026/10/20 9:29:53

用微信机器人框架实现爱心九宫格生成器:图片收发到切图全流程

用微信机器人框架实现爱心九宫格生成器:图片收发到切图全流程 前段时间在社区一个AI主题的创造营里我做了一件很“朋友圈风”的小工具用Wechaty搭一个机器人用户把一张照片发给它它自动把照片处理成爱心形状的九宫图然后按顺序把九张图发回来。发朋友圈的时候九张拼一起就是一颗完整的爱心。听起来不算复杂但真正从“能跑”到“能用”中间踩了不少坑。这篇就把整个思路、代码、坑和调整过程一起写出来给打算用Wechaty做图片类小工具的朋友做个参考。如果你只是想找个现成方案那其实没必要读下去但如果你打算自己动手做一个类似的机器人或者已经在用Wechaty做图像处理相关的功能这篇文章应该能帮你少走不少弯路。我会从需求拆解讲起把图片收发、蒙版切图、发送顺序这几个核心环节掰开揉碎最后再聊一些上线部署时容易忽略的细节。1. 从朋友圈九宫格玩法聊起需求拆解与方案选型1.1 这个机器人到底做了什么先明确一下“爱心九宫格生成器”到底是干什么的。用户在微信里给机器人发一张图机器人后台拿到这张图把图片裁成正方形套上一个爱心形状的蒙版让图片中间区域变成一颗爱心、四周变成透明背景。然后再把这张带透明通道的大图平均切成九小块按特定顺序通过聊天窗口发回给用户。用户保存这九张图按顺序发朋友圈九宫格拼起来就是一颗完整的爱心照片。这里有几个技术点其实是分离的微信消息的接入和图片收发由Wechaty负责爱心蒙版、裁切、九宫格切分由图像处理库负责最后的顺序发送又要回到Wechaty的消息发送接口。所以整个项目本质上是一个“消息流 图像流水线”的组合难度不高但对细节要求不少。我做这个工具的初衷很简单就是想在创造营里做一个和微信生态结合、又能在几周内跑通的小项目。朋友圈九宫格是大家都熟悉的东西爱心形状又是最容易出效果的视觉元素天然适合演示。1.2 为什么选Wechaty而不是小程序或网页工具需求确定之后我其实先纠结了一阵子这个功能用小程序做行不行用网页工具做行不行后来还是选了Wechaty。原因主要在于“使用路径”。如果做成小程序用户得搜索、打开、授权、上传图片、再下载九张图路径太长了。如果做成网页工具用户得先保存照片、再打开浏览器上传同样麻烦。而微信机器人是完全相反的体验用户就在聊天窗口里发一张图出去收到九张图回来全程不需要离开微信。这个体验对朋友圈场景来说几乎是零成本的。Wechaty本身是开源的聊天机器人框架支持多种消息协议也提供了统一的Message、Contact、FileBox等抽象。对于图片类小工具来说它的“图片接收”和“文件发送”能力刚好够用不需要自己处理底层的微信协议细节。我不建议一上来就自己搞协议解析和消息模拟那是另一个深坑。老老实实用框架把精力放在图像处理和流程稳定性上才是做这种小工具的正确姿势。方案用户操作路径开发成本适合场景微信小程序搜索、授权、上传、下载需要注册开发者账号审核周期长长期商业化产品网页H5工具存图、打开浏览器、上传、下载需要部署Web服务PC端或活动运营Wechaty机器人直接发图、收图较低本地/服务器即可运行熟人社交、朋友圈工具、尝鲜体验从表里能看出来如果只想快速验证一个小创意Wechaty的路径最短。当然个人号接入自动化有合规风险做这个项目建议只在测试环境和小范围试用不要大规模对外使用。2. 机器人如何收到图片并完成落盘Wechaty消息接入的几个关键点2.1 接收图片消息的必要判断Wechaty的接入套路很简单监听message事件判断消息类型如果是图片就走处理流程。但这里有几个初学者容易忽略的点。第一必须判断msg.type()是不是图片类型。微信里消息类型很多文本、图片、语音、视频、链接、表情如果不做类型判断后面的FileBox调用会直接报错或者拿到一个空对象。第二图片消息里拿到的文件有两种缩略图和大图。如果直接取toFileBox()得到的是当前消息对应的原始文件对象但实际传输可能被微信压缩过。一般来说通过msg.toFileBox()拿到的文件再调用fileBox.toFile()落地即可得到一个可处理的图片文件。第三下载过程可能是异步的文件没落地之前不要急着做图像处理。很多第一次写这种工具的朋友会忘记等待下载完成结果Pillow打开文件时拿到的还是一个空文件或者损坏文件。下面是一段极简的消息监听核心逻辑用的Wechaty TypeScript接口来示意import { WechatyBuilder, Message } from wechaty const bot WechatyBuilder.build({ name: heart-grid-bot, }) bot.on(message, async (msg: Message) { if (msg.self()) return if (msg.type() ! MessageType.Image) { await msg.say(发一张照片给我我来帮你做爱心九宫图) return } const fileBox await msg.toFileBox() const savedPath /tmp/heart-grid/${Date.now()}.jpg await fileBox.toFile(savedPath, true) // 这里再调用图像处理函数处理完再逐张发送 await processAndSend(msg, savedPath) }) bot.start()具体到微信不同类型的图片落地之后的格式可能是jpg也可能是gif。我们做蒙版处理时GIF动图要先取第一帧或者只处理静态部分否则后面Pillow打开时可能报错或者得到多帧对象。这个我在后面踩坑部分会展开。2.2 下载超时、缩略图与缓存目录图片下载有很多“看似很小、实际很伤”的细节我逐个说一下。第一下载超时。如果图片比较大或者网络不稳定toFile()可能迟迟不返回甚至超时。我一开始没有设置超时结果有用户在弱网环境发图整个流程卡在那里后续消息全部堆积。后来我在外层包了一个超时函数超过15秒就主动放弃并给用户提示“图片下载失败请重试”。第二缓存目录清理。因为每次都要写到临时目录时间久了会堆积大量图片文件。我建议每次处理完就把源文件删除只保留最终发送出去的九张图的临时文件等消息发送成功后再批量清理。如果不清理跑几天就能攒下上百MB的垃圾。第三文件名不能重复。很多机器人项目用固定文件名落盘比如temp.jpg如果同时收到两个人的图片后来的会覆盖之前的导致处理结果错乱。我后来统一用时间戳 随机后缀避免并发时的文件冲突。说到并发这里先提一个很重要的设计微信消息本身是顺序进入监听回调的但如果你的处理函数是异步的两条消息可能几乎同时处理。此时如果两个人同时发图临时文件就可能交叉读写。最简单的办法是加一个简单的“处理中”锁同一时间只处理一张图。虽然牺牲了一点并发能力但对这种个人小工具来说稳定性远比吞吐量重要。3. 把照片变成一颗“爱心”蒙版、裁切与九宫格切图3.1 爱心蒙版的生成思路整个工具的核心其实是图像处理而图像处理的核心是蒙版。我们的目标是把照片变成爱心形状四周完全透明所以必须用带Alpha通道的图像来存储结果也就是PNG格式。爱心蒙版本质上是一张黑白位图白色区域代表“保留原图”黑色区域代表“透明”。把照片和这张蒙版做一个合成运算就能得到带爱心形状的透明图。这里有一个关键点蒙版的边缘要做得平滑否则切成九格之后边缘会看到明显的锯齿。比如在蒙版生成后做一次高斯模糊再根据阈值重新二值化能得到更柔和的边缘或者在绘制爱心时使用超采样也就是把蒙版做大到2048甚至4096再缩小到1024这样抗锯齿效果非常好。爱心形状怎么来最简单的做法是直接用绘制函数生成。网上常见的爱心曲线公式是用参数方程x 16 * sin(t)^3 y 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t)在t从0到2π的范围内取足够多离散点就能画出一个标准爱心轮廓。然后把这个轮廓填充成白色背景设为黑色就得到了一张爱心蒙版。用Pillow实现的时候我建议先用ImageDraw在“L”模式的图上画轮廓再填充。不要直接画在RGB图上否则后面合成的时候还要转灰度多一步反而容易出错。from PIL import Image, ImageDraw import math def create_heart_mask(size1024): im Image.new(L, (size, size), 0) draw ImageDraw.Draw(im) points [] steps 2000 for i in range(steps): t 2 * math.pi * i / steps x 16 * math.sin(t) ** 3 y 13 * math.cos(t) - 5 * math.cos(2*t) - 2 * math.cos(3*t) - math.cos(4*t) # 映射到画布坐标同时放大并留边距 px int((x * size / 34) size / 2) py int((-y * size / 34) size / 2 size * 0.08) points.append((px, py)) draw.polygon(points, fill255) return im代码里的几个系数不是随手拍的34是爱心函数在x和y方向的最大跨度用来把爱心映射到整张画布上size * 0.08是Y轴向下偏移保证爱心在九宫格里看起来居中。这三个参数是调试裁切效果时花费时间最多的地方后面实测部分会细说。3.2 裁切到正方形后的合成与九格切分蒙版有了原图处理分成三步缩放裁切、合成、切九格。第一步把原图先缩放到一个尽量小的尺寸比如宽或高至少等于1024然后取中间区域裁成正方形。注意如果你不裁成正方形爱心蒙版里会有大片空白区域来自动补成透明切出来构图会很怪。裁切时可以稍微多留一点上半部分空间因为大头照通常在上面后面调取景时我还会再说。第二步合成。Pillow里最稳妥的方式是rgba Image.new(RGBA, (1024, 1024), (0, 0, 0, 0)) heart Image.composite(square_photo.convert(RGBA), rgba, mask)这里square_photo是先处理好尺寸的方图rgba是全透明底图mask就是上面生成的爱心蒙版。Image.composite会依据蒙版的灰度值决定显示上层还是底层白色区域显示照片黑色区域显示透明。第三步切九格。把合成好的1024x1024大图按行列切成3x3个格子每格大约341x341像素。切图本身不复杂用crop即可真正的坑在于“格子的边缘像素”和“发送顺序”是绑定的这一点我在下一章单独说。3.3 为什么要存成PNG而不是JPG很多人切完图后顺手存成jpg结果朋友圈发出来背景一片白色或者黑色完全没有透明效果。原因是JPG格式根本不支持透明通道透明部分会被填充成默认底色通常是白色或黑色。所以切完的九张图必须导出为PNG。同时要注意有些聊天工具发送PNG时会自动压缩或者转码实测下来微信PC端和手机端对PNG的容忍度比较高但超大PNG偶尔也会被拒收所以单张图片文件大小最好控制在1MB到2MB以内。一张1024x1024的PNG如果内容不复杂通常只有几百KB比较安全。下面是切九格的核心代码配合发送顺序一起看会更清楚def split_into_grid(heart_image, grid3): width, height heart_image.size cell_w width // grid cell_h height // grid tiles [] for row in range(grid): for col in range(grid): left col * cell_w upper row * cell_h right left cell_w lower upper cell_h tile heart_image.crop((left, upper, right, lower)) tiles.append(tile) return tiles这段代码返回的tiles列表顺序是“从左到右、从上到下”。但实际发送时我强烈建议你反着发原因看下一章。4. 发回顺序、尺寸比例和发送频率这三个坑我全踩了一遍4.1 朋友圈“反序”的实测结果九张图切好后逻辑上是按左上、上中、右上、左中、中心……一直到右下这个顺序发给用户朋友围发出来从上到下再从左到右刚好是一张完整图。但我第一次实测时发现朋友圈渲染出来的九宫格顺序完全反了。我知道很多人看到这里会说“你直接倒序发送不就行了”。对理论上就这么简单但问题在于微信在不同端、不同版本的排序规则可能不完全一致不能只看一次结果就下结论。我当时在手机端和客户端分别测试发现两种端里“接收到的九张图”在朋友圈里的排列顺序是相同的都是第一条消息排在最后一位。这就意味着代码里最后发送的那张图会出现在朋友圈九宫格的左上角。为了验证这个规则是稳定的我连续用三个微信号各发了一轮测试图确认是一致的。于是最终的做法很简单代码里先发送右下角的格子再发送下中、左下……最后发送左上角的格子。这样朋友圈里显示出来的顺序就是正常的九宫格顺序。你如果自己实现建议先发一张测试图确认当前环境下的排序规则再决定是否倒序。这里有一个更稳妥的办法不要直接在代码里写死顺序而是把“顺序策略”抽成一个配置项比如send_order字段。如果未来某个客户端版本改了规则改配置即可不用重新部署整套逻辑。4.2 尺寸比例与透明区域的细节九张图拼起来的时候理想情况下每张图片之间是没有缝隙的。但实际保存图片时如果切图尺寸不是严格的1:1正方形或者发送过程中被聊天工具二次压缩拼起来就会出现细缝或者边缘错位。我实测下来的几个数据供参考原图处理尺寸建议用1024x1024这样可以兼顾清晰度和文件大小切图后每张尽量保持正方形微信对图片会做居中裁切展示如果比例不正边缘会被裁掉发送时不要给图片加白边、圆角或者阴影效果任何额外修饰都会在拼接时产生视觉缝隙。另外透明背景的图片发到聊天窗口里用户在对话框看到的是“灰色或白色格子”。很多人会误以为图片有问题所以我在收到图片后的自动回复文案里加了一句“请选择九张图时注意保留透明部分不要套用滤镜或编辑”。这个小细节能省掉大量“图片坏了”的用户反馈。4.3 频率控制与稳定性连续发送九张图其实是在很短的时间内发九条消息。微信对高频消息是有风控的连续发图尤其容易触发限制。第一次上线时我在循环里直接发送没有做间隔结果发到第五张的时候被系统拦了用户只收到五张图体验非常奇怪。后来我在每次发送之间增加了一个随机延迟基础延迟800毫秒再加一个0到500毫秒的随机抖动。这样九张图发送完大概需要10秒左右用户体验上稍微慢一点但稳定性好了很多没有再出现发送中断。延迟代码大概是这样import time, random for tile in ordered_tiles: await send_image(tile) time.sleep(0.8 random.random() * 0.5)注意如果你的机器人是单进程处理消息这个延迟会阻塞后续其他用户的处理请求。所以更合理的做法是把发送任务放到队列后台执行收到消息后立刻回复“收到正在生成请稍后”然后异步去处理、再异步发送。这样用户不会觉得机器人卡住了同时也能避免并发时延迟互相影响。5. 实测效果与调整从“能用”到“好用”的参数打磨5.1 三个真实用户带来的问题第一版在小范围群里放出来后大概有三类反馈每一类都指向一个具体问题。第一类反馈是“图片主体位置不对”。有的人发的照片里人物偏左爱心形状虽然正确但九宫格拼出来后人物被爱心边缘切掉了一半脸。这个问题出在“无脑居中裁切”上我们的裁切策略是取正方形中心区域但很多竖构图照片的人物本来就在中间偏下或偏上位置直接取中心就会砍掉关键信息。第二类反馈是“图片内容太杂爱心形状不明显”。有些用户发的是带有大量背景的照片爱心区域的轮廓被淹没。这个倒不完全是问题毕竟爱心是形状上的玩法但如果照片太亮边缘过渡就不明显导致看起来像一张普通方格图。第三类反馈最意外有用户把九张图保存后在“最近编辑”里直接点了滤镜结果透明背景全部变成了黑色九宫格拼出来是一颗黑心。本质上还是很多用户不理解透明通道这一点只能靠文案提示了。5.2 取景优化帮用户自动找主体为了解决“位置不对”的问题我给裁切环节加了一个简单的主体检测。不是上深度学习模型而是用了最基础的“显著性区域”判断把原图缩到很小比如64x64转成灰度图然后分成横三竖三的九块区域计算每一块的像素方差。方差最大的一块往往就是纹理最丰富、最可能包含主体的区域。根据这个区域的位置再决定裁切框向上、向下还是居中偏移。实现思路不复杂效果却出乎意料地好因为大多数用户照片的主体就是人脸或物体纹理复杂度天然高于天空、墙壁这类纯色背景。如果你对准确率要求更高可以用现成的轻量级目标检测模型来定位人脸或物体但为了一个练手项目我建议先上“方差法”。够用、没有额外依赖、运行也快一张图处理下来不到50毫秒。5.3 异常兜底与用户提示还有一类用户会上传一些奇怪的内容比如截图、表情包、文档扫描件。这些图片在缩放裁切后爱心效果往往不理想但也不会让程序报错。更需要注意的是上传超长图、GIF动图、或者那种分辨率巨大的全景图。我在处理前增加了一个参数检查步骤图片格式不支持时直接catch异常并回复“图片有点特殊我没法处理换一张试试”图片文件过大时先尝试压缩到长边不超过2000px再处理图片是GIF时只取第一帧。这些边界处理写起来不复杂全部做完之后工具的使用体验才真正让人觉得“稳了”。6. 上线部署与扩展思路把它从“练手作品”变成“常驻服务”6.1 运行环境与挂机注意事项做到这一步工具在本地跑已经没问题了。但如果你想让它常驻运行有几个部署层面的问题必须考虑。第一运行环境。微信机器人需要长期在线所以你得有一台24小时开机的机器树莓派、云主机或者旧手机都行。我建议不要放在Windows桌面机器上因为休眠、断电都会让机器人掉线而且重启后还要处理扫码重新登录的问题。第二自动重启。进程崩溃是不可避免的用进程守护工具把机器人拉起崩溃后自动重启。否则哪天晚上挂了第二天白天用户消息全部丢失。第三日志与监控。多打日志尤其是图片下载、处理耗时、发送耗时这三步。出问题时能快速定位是网络问题、图像处理问题还是发送被限制。6.2 并发处理、隐私清理与内容安全并发处理是这类机器人从小白走向可用的一道坎。最开始没有锁两三个人同时发图处理结果互相串了。后来我给每个会话加了串行锁同一时间只处理一张图其余消息排队等待。隐私方面因为这个工具会接收用户的相册照片属于比较敏感的个人信息。我建议处理完图片后立即删除源文件和临时文件不要长期留存。至少也要做到“每天定时清理临时目录”并在日志中隐藏文件完整路径和用户名。内容安全也要考虑。如果这个机器人开放给不认识的人使用理论上别人可以发任何内容你实际上是在帮他们处理和传播内容。对于个人练手项目建议加白名单机制只允许指定的联系人或测试群使用别一上来就面向全量用户开放。6.3 值得继续做的扩展方向做完爱心九宫格我手里掌握的核心能力其实是“接收图片 → 图像处理 → 异步发回多张图”这套流程可以不变只替换图片处理模块就能扩展出很多玩法。比如文字签名图用户发一段文字机器人生成一张好看的文字背景图再发回来。再比如纯色头像九宫格用户选择喜欢的颜色机器人自动生成一组渐变色的九宫格头像。要是再进一步还可以做模板市场爱心、兔子、圣诞、雪花每个模板对应一套蒙版用户发“爱心”或“圣诞”关键词来选择。这次做完之后我最大的感受是看起来很不起眼的小功能真正要把细节全部处理好工作量比想象中大得多。尤其是发送顺序和透明通道这两个问题不实际发一轮朋友圈光靠看代码是发现不了的。如果你也在做类似的Wechaty图片工具建议先把“收图—处理—发送”三个环节各自独立测试再串起来联调最后再做小范围真实用户测试。这个顺序能帮你省掉大量排错时间。