迹录相机:我做了一款面向现场工作记录的微信水印相机

迹录相机:我做了一款面向现场工作记录的微信水印相机
Kay
在信息化运维、设备巡检、工程施工、活动保障以及各种现场工作中,拍照留档几乎已经成为一种标准流程。
但普通照片能够记录的,通常只有当时的画面。
当这些照片真正进入工作记录、项目归档甚至后续追溯流程以后,经常还需要回答更多问题:
- 这张照片是什么时候拍摄的?
- 当时具体位于什么位置?
- 拍摄时的天气和现场环境如何?
- 是谁完成了这次处理?
- 属于哪个项目或者哪项工作?
- 照片是现场实时拍摄,还是从相册导入后添加的水印?
- 照片保存以后有没有被编辑?
- 水印内容有没有被重新覆盖或者修改?
如果只依靠手机系统相机,这些信息往往需要另外整理。
因此,我开发了一个自己的微信小程序水印相机:
「现场迹录」
它的目标并不只是给照片左下角增加一行时间和地点,而是尝试把:
现场拍摄 → 信息水印 → 自定义模板 → 高清合成 → 来源存证 → 照片验真
组合成一套相对完整的现场影像记录流程。
官方网站:
1 | https://shuiyin.nnu.cn/ |
项目仓库:
1 | https://github.com/anyfrees/xianchang-jilu |
一、为什么要重新做一个水印相机
最初做这个项目的原因其实很简单。
在日常现场工作中,经常需要留下类似这样的照片记录:
1 | 某单位 |
这种照片最大的价值,并不是“好看”,而是能够快速说明:
谁,在什么时间、什么地点,完成了什么工作。
市面上当然已经存在很多成熟的水印相机。
但真正应用到自己的工作场景以后,我逐渐发现几个问题。
1. 固定模板很难适应不同工作
设备巡检可能需要:
1 | 设备名称 |
故障处理需要:
1 | 故障类型 |
活动保障又可能需要:
1 | 活动名称 |
如果所有场景都使用同一套固定水印,很快就会出现大量无用字段。
所以我更希望:
水印本身就是可以设计的。
2. “有水印”并不代表“现场拍摄”
一张照片完全可以:
- 从其它地方获取;
- 导入水印应用;
- 修改时间;
- 修改位置;
- 添加工作信息;
- 再重新导出。
最终看起来,可能和现场实时拍摄的照片没有明显区别。
因此:
有水印 ≠ 现场原拍。
这一点后来也成为“现场迹录”继续加入照片来源识别和验真功能的重要原因。
3. 我更想要一个工具,而不是一个内容平台
对于现场工作来说,我更需要的是:
1 | 打开 |
而不是复杂的账号体系、社交功能或者大量与拍照无关的功能。
因此项目从最开始就比较强调:
轻量、直接、原生。
二、现场迹录是什么
“现场迹录”目前是一套由:
原生微信小程序 + 官方网站
组成的现场影像记录工具。
微信小程序负责实际的:
- 相机拍摄
- 水印实时预览
- 地理位置
- 天气信息
- 自定义水印
- Logo
- 相册导入
- 高清照片合成
- 照片来源存证
- 照片验真
官方网站则作为项目的 Web 入口,用于承载项目相关的信息和后续服务。
官网:
1 | https://shuiyin.nnu.cn/ |
目前小程序主要包含这些页面:
1 | camera |
也就是说,它实际上已经不只是一个简单的“拍照页面”。
项目正在逐渐变成一套围绕:
现场影像
展开的工具链。
三、为什么选择原生微信小程序
整个项目目前采用原生微信小程序开发。
主要使用:
1 | WXML |
没有使用:
1 | Vue |
等跨端框架。
也没有引入大型第三方 UI 框架。
对于普通业务应用来说,框架能够明显提升开发效率。
但相机类小程序比较特殊。
项目大量依赖微信原生能力,例如:
1 | Camera |
直接使用微信原生 API,反而能够减少中间抽象层。
另外,项目目前没有 npm 运行依赖。
整体技术栈保持得比较简单。
四、实时相机与水印预览
打开小程序以后,会直接进入相机界面。
相机页面不仅维护 Camera 状态,同时还会实时维护当前:
1 | 时间 |
这些数据随后都可以参与水印绘制。
我希望实现的体验是:
拍摄之前就能够大致看到最终照片是什么样子。
因此水印不是拍完以后才突然出现,而是在 Camera 取景阶段进行实时预览。
这样用户可以提前判断:
- 水印是否挡住主体;
- 字体是否太大;
- Logo 是否影响画面;
- 当前字段是否合适;
- 水印应该放在哪个位置。
五、时间和日期格式可以自由调整
虽然时间水印看起来很简单,但不同场景对格式的需求实际上并不完全一样。
例如时间可以显示:
1 | 14:28 |
日期可以显示:
1 | 2026.08.14 |
对于正式的工作归档,可以显示:
1 | 2026.08.14 14:28:36 |
而对于偏视觉化的照片,也可以采用:
1 | 08月14日 |
这样的简化样式。
时间和日期格式会跟随水印方案保存。
六、自动定位与地点识别
对于现场工作照片而言:
位置通常和时间一样重要。
小程序会调用微信位置能力获取当前位置。
主要使用:
1 | getLocation |
得到经纬度以后,再调用腾讯位置服务进行逆地址解析。
这里我没有单纯追求显示一个非常长的完整地址。
例如:
1 | 某省某市某区某街道某号…… |
虽然信息完整,但放在照片上可读性并不好。
因此更希望优先得到附近具有实际意义的 POI。
比如:
1 | 某学校 |
这类名称往往比完整行政地址更适合作为现场照片水印。
如果自动定位结果不准确,也可以调用微信地图进行手动选点。
七、天气、海拔和定位精度
除了位置,我还增加了一些可选现场信息。
天气
例如:
1 | 晴 31℃ |
或者:
1 | 多云 28℃ |
对于:
- 户外施工
- 工程巡检
- 活动保障
- 设备检查
来说,天气实际上也是现场环境的一部分。
海拔
部分场景可以开启:
1 | 海拔 36m |
普通工作可以关闭。
只有需要时才显示,不强行占用水印空间。
定位精度
还可以显示类似:
1 | 定位精度 ±8m |
这项信息对于普通拍照没有太大价值。
但如果照片被用于现场记录,那么能够知道当前 GPS 定位的大致可信范围,会更严谨一些。
八、水印不是模板,而是一张可以设计的“卡片”
这是项目后来变化最大的一部分。
最开始我也准备像普通水印相机一样:
1 | 模板1 |
不断增加固定模板。
后来发现这种方式很快就会失控。
因为每增加一种工作场景,就需要再设计一套新模板。
所以我最终加入了:
自定义水印卡片设计器
用户可以自己设计水印。
例如:
1 | 某单位 |
设计完成后保存为自己的水印方案。
下一次打开相机,可以直接调用。
九、自定义卡片设计器
设计器支持不同卡片比例。
包括:
1 | 横向长条 |
程序会根据水印比例动态计算画布尺寸,而不是简单拉伸图片。
水印可以包含:
1 | 标题 |
每一个字段都可以独立控制:
1 | 位置 |
另外还加入了:
1 | 拖拽 |
等编辑能力。
它现在已经比较接近一个:
轻量级水印排版工具。
十、水印背景和装饰层
如果只是文字叠加,很难形成统一视觉效果。
因此自定义卡片可以导入自己的背景素材。
例如不同工作类型可以设计成不同视觉方案:
1 | 故障处理 —— 蓝色 |
背景可以调整:
1 | 透明度 |
同时还存在一个独立的装饰层。
这样水印实际可以拆成:
1 | 背景 |
不同层级。
这样企业、学校、项目组甚至个人,都可以制作自己的水印视觉系统。
十一、Logo 裁剪与独立调整
很多工作照片都会使用单位 Logo。
但是直接选择一张 Logo 图片以后,经常会出现:
1 | 透明区域太大 |
所以项目专门增加了:
Logo 裁剪
页面。
Logo 可以在导入以后先进行裁剪和处理。
随后在水印中继续调整:
1 | 显示 / 隐藏 |
这样 Logo 就可以真正融入整个水印设计,而不是简单贴在图片旁边。
十二、自定义字段和人员预设
实际工作中,有些内容需要每次改变。
例如:
1 | 项目: |
如果每拍一张照片都重新输入,会非常麻烦。
因此设计器中的部分字段可以定义为:
数据字段。
例如模板中定义:
1 | 处理人: |
实际拍照时只需要选择:
1 | 运维人员A |
最终生成:
1 | 处理人:运维人员A |
对于一些人员类字段,例如:
1 | 成员 |
还支持多选。
例如:
1 | 现场人员:工作人员A、工作人员B、工作人员C |
常用人员可以提前作为预设保存。
这样现场拍摄时就不需要频繁输入文字。
十三、实时预览不等于最终图片
相机类应用有一个很容易忽略的问题:
屏幕看到的东西不能直接当成最终照片。
如果直接截取 Camera 页面:
- 分辨率会降低;
- 水印文字可能发虚;
- 不同设备屏幕比例会影响结果;
- 最终照片尺寸无法保证。
因此“现场迹录”使用的是:
Camera 负责取景
Canvas 2D 负责最终高清合成
拍照以后,大致处理流程为:
1 | Camera 拍摄原图 |
这意味着最终水印并不是简单截图。
而是按照原始照片尺寸重新绘制。
十四、4096px 与大图片内存控制
手机拍摄照片的分辨率越来越高。
一些设备拍摄出来的照片甚至可以达到:
1 | 4000px+ |
如果直接按照原图尺寸创建超大 Canvas,很容易造成:
1 | 内存占用过高 |
因此目前项目会对输出尺寸进行限制。
照片最长边大致控制在:
1 | 4096px |
再以较高 JPEG 质量导出。
这样能够在:
画质
和:
移动端内存
之间取得比较合理的平衡。
十五、为什么还要支持相册导入
微信小程序 Camera API 并不能完全替代手机系统相机。
例如手机系统相机可能拥有:
1 | HDR |
等能力。
而微信小程序 Camera 能够控制的硬件能力有限。
特别是在部分设备上,虽然手机拥有多个物理摄像头,但小程序并不能像系统 Camera 一样自由选择:
1 | 0.5× |
所有物理镜头。
因此项目保留了:
相册导入
能力。
用户可以:
1 | 系统相机拍摄 |
这样能够利用手机系统相机完整的摄影能力。
但这里产生了另一个重要问题。
十六、现场原拍和相册加水印必须区分
从相册导入的照片,即使最终水印看起来完全一样,也不能被解释成:
现场实时拍摄。
因此项目会记录照片来源类型。
现场 Camera 实时拍摄可以标记为:
1 | live-camera |
而相册导入,则属于:
1 | album-watermarked |
两种来源在验真页面中会被区别显示。
例如相册照片会明确显示:
相册照片,后期加水印。
而不会显示:
现场原拍。
我认为这是整个设计中比较重要的一点。
因为如果连来源都不区分,那么后续的“验真”其实没有太大意义。
十七、给水印相机加入照片验真
随着项目继续开发,我后来又增加了:
照片验真
页面。
用户可以重新选择一张“现场迹录”生成的照片进行验证。
这部分目前并不是简单读取图片 EXIF。
因为 EXIF:
- 很容易被清除;
- 可以被修改;
- 很多平台转发以后会直接删除。
因此项目采用的是多种机制组合判断。
目前涉及:
1 | SHA-256 |
它们解决的问题并不完全相同。
十八、SHA-256:判断是不是同一个文件
照片保存以后,会生成 SHA-256 文件指纹。
例如:
1 | 9b1d5c…… |
如果文件任何一个字节发生改变,那么 SHA-256 通常就会完全变化。
因此 SHA-256 非常适合回答:
当前这个文件是不是当初登记的那一份文件?
如果 Hash 完全一致,可以得到非常强的文件完整性结果。
但是 SHA-256 也存在明显问题。
例如照片被:
1 | 系统相册重新编码 |
以后,即使肉眼看起来没有任何变化:
1 | SHA256 |
仍然会改变。
所以单纯依赖 SHA-256 是不够的。
十九、Visual Hash:判断还是不是那张照片
因此还加入了:
Visual Hash
可以把它简单理解成:
SHA-256 判断:
文件是否完全相同。
Visual Hash 判断:
视觉内容是不是仍然是那张照片。
例如照片经历:
1 | JPEG 压缩 |
只要主体内容没有明显变化,视觉特征仍有可能匹配。
因此系统可以进一步区分:
1 | 原始文件完全一致 |
和:
1 | 来源匹配,但文件已经重新编码 |
而不是简单显示:
1 | 成功 |
二十、区域完整性检测
只比较整张图片的视觉特征仍然不够。
因为有人完全可能只修改照片一个很小的区域。
例如:
1 | 增加马赛克 |
这种情况下:
整张图片仍然非常相似。
因此项目又加入了区域完整性特征。
把图片划分成多个区域以后分别进行特征判断。
如果发现某些区域和原始存证明显不同,可以返回类似:
1 | CONTENT CHANGED |
并提示:
照片内容可能已经发生修改。
这样就比“整张图看起来差不多”更加严格。
二十一、Blind Watermark:隐形来源标记
除了肉眼能够看到的水印之外,项目还加入了:
Blind Watermark
也就是:
隐形盲水印。
它不是:
1 | 2026.08.14 |
这种用户肉眼能够直接看到的文字。
而是一种隐藏的照片来源标记。
验真时,小程序会尝试重新提取这个标记。
如果:
1 | 文件信息 |
都能够相互对应,就能够进一步提高来源判断的可信度。
如果:
1 | 盲水印存在 |
但:
1 | 照片内容完全不匹配 |
系统也不会直接判断为正版。
而是会返回类似:
1 | MARKER DETECTED |
表示:
检测到了迹录标记,但当前照片内容与对应存证并不匹配。
二十二、水印区域本身也需要验证
还有一种情况更加值得注意。
照片主体完全不修改,只修改:
水印。
例如:
原本:
1 | 2026.08.14 |
被改成:
1 | 2026.08.12 |
或者:
1 | 地点:某教学楼 |
被改成另外一个地点。
从图片主体来看,这两张照片几乎完全一样。
所以项目会单独记录:
水印区域特征。
如果主体照片能够匹配,但水印区域发生明显变化,则可以返回:
1 | WATERMARK CHANGED |
提示:
照片来源可能匹配,但是水印内容已经发生变化。
对于工作记录来说,我认为这一点甚至比检测照片主体变化更加重要。
因为:
时间、地点、人员和工作内容本身就是照片记录的一部分。
二十三、为什么转发后的照片不能说“100%未修改”
这是整个照片验真系统里比较容易产生误解的地方。
照片经过聊天软件发送以后,平台很可能会重新:
1 | 编码 |
因此即使用户什么都没有修改:
1 | 文件 Hash |
也可能和原图完全不同。
在这种情况下,如果程序仍然直接提示:
照片完全未修改。
其实并不严谨。
所以系统会出现类似:
1 | 来源已确认 |
这样的状态。
意思是:
可以确认照片与某条现场迹录存证存在来源关系。
但:
无法继续宣称当前这个重新编码后的文件和原始文件字节级完全一致。
整个验真系统希望遵循的原则就是:
能证明多少,就显示多少。
而不是为了让结果看起来漂亮,就简单显示一个绿色的:
1 | 100%真实 |
二十四、验真不是简单的“真”和“假”
目前验真页面会尝试区分多种情况。
例如:
JILU AUTHENTIC
表示:
1 | 文件指纹 |
能够匹配。
可以认为是迹录相机登记的原始照片。
JILU SOURCE VERIFIED
表示:
来源已经确认,但图片经过系统相册或者其它方式重新编码。
ALBUM WATERMARKED
表示:
这是一张从相册导入后,由现场迹录添加水印的照片。
可以确认:
经过现场迹录处理。
但不能把它解释为:
现场实时拍摄。
CONTENT CHANGED
表示:
检测到画面部分区域和原始存证存在明显差异。
WATERMARK CHANGED
表示:
主体可能匹配,但水印区域发生变化。
MARKER DETECTED
表示:
检测到迹录盲水印,但照片内容与对应存证不匹配。
这种分级方式比单纯:
1 | 真 |
更加符合实际情况。
二十五、照片存证并不意味着上传所有照片
提到照片验真,很容易让人产生一个误解:
是不是每拍一张照片,都需要把完整原图上传服务器?
目前我的设计思路并不是这样。
项目更希望存储用于后续验证的:
1 | 文件 Hash |
等验证数据。
而不是默认把用户所有工作照片集中上传。
项目目前也已经拆分了:
1 | contentSecurity |
等相关服务逻辑。
二十六、隐私设计
水印相机天然会接触到比较敏感的数据。
例如:
1 | 相机 |
因此隐私一直是这个项目必须考虑的问题。
水印设置、自定义方案以及一部分素材主要保存在小程序本地 Storage。
对于服务端,只应该提交完成对应功能所必要的数据。
而且:
照片来源验证不能成为无限制收集照片内容的理由。
这是后续开发中仍然需要持续关注的地方。
本文所有演示内容也已经进行了匿名化处理。
出现的:
1 | 某单位 |
等均为演示数据,不对应实际人员或者工作地点。
二十七、手机方向与水印旋转
相机类小程序还有一个比较麻烦的问题:
设备方向。
用户可能:
1 | 竖屏拍摄 |
但是不管用户怎么旋转手机,最终水印都应该保持正确阅读方向。
因此项目会结合设备方向信息计算:
1 | watermarkRotation |
当设备方向改变时重新计算:
1 | 水印位置 |
避免出现:
1 | 照片已经横过来了 |
这种情况。
二十八、不同手机的安全区域
微信小程序顶部存在系统胶囊按钮。
不同型号的:
1 | iPhone |
顶部区域都不完全相同。
所以相机页面不能把:
1 | 顶部按钮位置 |
全部写死。
项目会读取:
1 | wx.getMenuButtonBoundingClientRect() |
以及窗口信息,动态计算:
1 | 导航高度 |
这一点对于沉浸式 Camera 页面非常重要。
二十九、变焦能力与物理镜头限制
Camera 会根据真机返回的:
1 | maxZoom |
动态提供变焦入口。
例如:
1 | 1× |
如果当前设备不支持某个倍率,相应按钮就会禁用。
不过需要注意:
微信小程序提供的 Camera 能力,并不等于系统 Camera。
例如手机可能拥有:
1 | 0.5× 超广角 |
但小程序没有统一公开接口可以自由选择所有物理摄像头。
所以需要特定镜头时,系统 Camera + 相册导入仍然是更加现实的方案。
三十、内容安全
因为用户可以自由编辑:
1 | 标题 |
所以内容安全也是必须处理的问题。
项目已经加入相关文本安全检查机制。
例如:
1 | checkTextSecurity |
在用户保存或者使用部分自定义内容之前进行检查。
对于公开运行的小程序来说,这是非常必要的。
三十一、它更适合哪些场景
虽然它本质上是水印相机,但我并没有打算把它做成普通自拍或者生活打卡工具。
目前更希望服务的是:
工作现场。
比如:
信息化运维
1 | 网络故障处理 |
校园运维
1 | 教室设备检查 |
工程施工
1 | 施工前 |
物业巡检
1 | 消防设备 |
售后维护
1 | 到场记录 |
活动保障
1 | 会场准备 |
这些场景最大的共同点是:
照片不仅是一张照片,也是一次工作记录。
三十二、当前项目结构
项目目前大致结构如下:
1 | xianchang-jilu |
其中:
1 | camera |
负责主要的 Camera 和水印逻辑。
1 | card-designer |
负责自定义卡片设计。
1 | logo-crop |
负责 Logo 素材处理。
1 | preview |
负责最终照片预览。
1 | verify |
负责照片来源和完整性验证。
三十三、本地运行需要什么
如果想自己运行项目,首先需要准备一个微信小程序 AppID。
然后使用:
微信开发者工具
导入项目。
定位部分需要申请相关接口:
1 | getLocation |
同时需要申请腾讯位置服务 WebService API Key。
用于完成:
1 | 经纬度 |
腾讯地图 API 域名还需要加入微信小程序:
1 | request 合法域名 |
发布前还必须完成:
1 | 隐私保护指引 |
三十四、真机测试比开发者工具重要得多
相机类小程序无法只依赖微信开发者工具进行验证。
至少需要:
一台 iOS 真机
和:
一台 Android 真机。
需要重点测试:
1 | Camera |
对于 iOS,还要特别关注:
1 | 系统相册重新编码 |
对于 Android,则需要关注不同厂商:
1 | Camera 实现差异 |
这种项目真机兼容性测试的工作量,实际上远高于普通信息展示型小程序。
三十五、后面还想继续做什么
“现场迹录”目前仍然处于持续开发阶段。
后续还有很多可以继续完善的方向。
1. 官方模板库
希望后续提供更多可直接安装的模板。
比如:
1 | 信息化运维 |
用户不需要从零设计。
直接安装以后修改单位名称和 Logo 即可。
2. 单位统一模板
例如企业或者项目组可以统一维护:
1 | Logo |
现场工作人员只负责填写数据。
这样同一个项目拍出来的照片能够保持统一风格。
3. 一次任务,多张照片
现实中的巡检通常不会只拍一张照片。
例如一次设备巡检可能连续拍摄:
1 | 20 张 |
如果每张照片都重新填写:
1 | 项目 |
效率会非常低。
所以后续比较适合加入:
现场任务
概念。
先创建:
1 | 任务名称 |
随后拍摄的所有照片自动继承这些信息。
4. 自动生成工作报告
更进一步,可以把:
1 | 照片 |
自动整理成:
现场工作报告
例如最终导出:
1 |
|
这样它就不再只是一个:
水印相机。
而是进一步变成:
现场工作记录工具。
5. 继续完善照片验真
照片验真部分仍然有很多值得研究的方向。
例如:
1 | 时间可信度 |
等等。
这部分我也会继续根据实际使用情况迭代。
三十六、关于“照片真实性”的边界
最后还需要特别说明一点。
任何一个普通应用都不应该轻易宣称:
一张照片绝对真实。
照片真实性本身涉及非常复杂的链路:
1 | 拍摄设备 |
任何一个环节都可能影响最终结果。
因此“现场迹录”的目标并不是建立一个所谓:
绝对可信的数字司法取证系统。
它更加现实的目标是:
尽可能记录照片从哪里产生,并尽可能判断照片后续是否仍然与原始记录一致。
因此系统会区分:
1 | 现场实时拍摄 |
1 | 相册导入后加水印 |
1 | 原文件完全一致 |
1 | 来源确认但已经重新编码 |
1 | 内容疑似修改 |
1 | 水印疑似修改 |
而不是粗暴地只显示:
1 | 真 |
或者:
1 | 假 |
我认为一个工具最重要的不是把自己的能力说得有多强,而是:
清楚说明它能够证明什么,以及不能证明什么。
写在最后
刚开始做“现场迹录”的时候,我只是想做一个:
没有多余功能、可以自己设计水印的微信小程序。
真正开始写以后,却发现一个看似简单的水印相机,背后实际上涉及很多技术问题。
从:
1 | Camera |
到:
1 | Canvas |
从:
1 | GPS |
到:
1 | 逆地址解析 |
从:
1 | 自定义水印 |
到:
1 | 照片来源存证 |
再到:
1 | SHA-256 |
项目慢慢从一个简单的:
照片加水印工具
发展成了现在这套:
现场拍摄 → 信息水印 → 高清合成 → 来源存证 → 照片验真
的完整流程。
这也是为什么最后给它取名:
「现场迹录」
“迹”,是现场留下的痕迹。
“录”,是对一次工作过程的记录。
我希望它最终解决的,不只是:
怎么在照片上加一行文字。
而是:
如何让每一次现场,都留下更加清晰、规范并且可以追溯的记录。
项目信息
项目名称
1 | 现场迹录 |
项目类型
1 | 原生微信小程序 + 官方网站 |
官方网站
1 | https://shuiyin.nnu.cn/ |
GitHub
1 | https://github.com/anyfrees/xianchang-jilu |
主要技术
1 | WXML |
主要功能
1 | 实时水印拍摄 |
「现场迹录」目前仍在持续开发和优化中,部分功能、接口、视觉效果以及照片验真策略会随着实际使用情况继续调整。
本文出现的单位名称、工作地点、人员姓名和项目数据均已进行匿名化或使用虚构示例,不对应真实人员及实际工作地点。













