图片是不是AI生成的?我用开源工具实现了8种检测算法
写了个开源项目,纯离线检测图片是不是AI生成的,今天来分享一下工具背后使用的算法。
背景是这样的,现在网上的图片真真假假根本分不清,有时候自己用 API 生图的时候也不知道它背后是否把模型掉包了,一是需要识别是否为 AI 生成,二就是判断是哪家 AI 生成的。
这个工具叫 apimart-cli,我开源在 GitHub 上了。它本来是一个对接 OpenAI/OpenRouter 的 API 客户端,后来我给它加了一个 detect 命令,离线检测图片是不是 AI 生成的。不需要联网,不需要 API Key。
它到底怎么实现的呢?这篇文章就说说背后的八种检测信号。
AI 生成的图片都有一定之规,它和真实照片区别还是挺大的,主要是三个层面。
第一层,元数据层面。AI 工具生成的图片,大部分会在文件里写入一个"身份证",标注"这张图是 AI 生成的"。比如 OpenAI 的 DALL-E、Google 的 Imagen,都在遵循 C2PA 标准。这是最直接的证据。
第二层,像素层面。AI 模型生成的图像,在像素上会有统计学上的"特征"——比如噪声分布太均匀、高频成分太规律、或者 JPEG 压缩的参数不标准。这些痕迹肉眼看不出来,但算法能。
第三层,模型层面。用 AI 来检测 AI——训练一个视觉模型,让它见过海量 AI 图和真实照片,然后让它判断新的图更像哪一边。
我的 detect 命令把这三层拆成了 8 个信号,加权融合,最终输出一个 0-99% 的置信度评分,配一个 emoji。🟢🟡🟠🔴🤖,一目了然。
铁证信号:C2PA 和 TC260
这是"一票否决"的信号。
C2PA 全称 Coalition for Content Provenance and Authenticity,一个行业标准联盟,成员包括 Adobe、Microsoft、OpenAI、Google 这些。它的做法是给图片嵌入一份防篡改的元数据,记录"谁用哪个软件生成的这张图"。
从 PNG 或 JPEG 的元数据里读到 C2PA manifest,如果里面写着 Source: AI Generated,基本就是实锤了。
另一个是中国的国家标准 GB 45438-2025,也叫 TC260 AIGC 标签。字节跳动的豆包、智谱的 GLM、百度的文心一言,生成图片时都会写入这个标签。我维护了一份 ContentProducer 编码对照表,能直接查出是哪家厂商生成的。
这两个信号只要命中一个,直接输出 🤖 99%,不再看其他信号。
SynthID 推断
Google 的 SynthID 是一个不可见水印技术,嵌入图片像素里,人眼看不出,但算法能检测出来。
但问题是 Google 没有公开 SynthID 的解码算法。我这里取了个巧——通过 C2PA 的 Vendor 字段来推断。
如果 C2PA manifest 里写着 Signed by Google 或者 Software: Imagen,就基本可以判定这张图带有 SynthID 水印。虽然不能 100% 确认,但结合其他信号,它是一个很强的参考。
Camera EXIF:还原真相反向信号
真实照片最大的特征是什么?有相机信息。
JPEG 的 EXIF 里记录了快门、光圈、ISO、镜头型号——这些数据是物理世界的痕迹,AI 不会自己生成。
解析 EXIF 花了我不少功夫。Go 的标准库不直接支持 EXIF 解析,得从 TIFF header 开始手写解析器,遍历 IFD 的各个 tag。代码里有 readExifCamera 这个函数,超过 100 行,干的就是这件事。
有相机信息会大幅拉低 AI 评分,没有的话会变成一条弱信号"没有相机 EXIF——很可能不是真实照片"。
ONNX ViT 模型:用 AI 检测 AI
这是最"硬核"的一条信号。
下载一个 ViT-Base 模型(86M 参数,327MB),通过 ONNX Runtime 在本地做推理。图片缩放到 224x224,经过双线性插值、归一化、转成 CHW 格式,喂给模型,得到一个 0-1 的概率值。
纯 Go 写的推理代码,零 CGO 依赖。我用的是 pure-onnx 库,只需要一个动态库文件(Windows 上是 onnxruntime.dll,Linux 上是 libonnxruntime.so,也有 Mac 版本)。
不过说实话,这个模型的准确率是没法作保证的,毕竟模型体量在那儿。不过它是来自 HuggingFace 上的开源模型,开源即正义。所以它的权重我设得中等(20 分),作为一个补充信号。
FFT 频谱分析:看图的高频能量
FFT 是傅里叶变换的意思,这个我比较得意,纯数学方法,不需要任何模型。
先把图片转灰度,缩放到最大 512 像素。然后用 Go 的 gonum/dsp/fourier 库做 2D FFT。
具体做法是行列分离法:先对每一行做一次 FFT,再对每一列做一次。结果是一个二维频谱图。
从频谱里提两个特征:
高频能量比:空间频率最高的 30% 区域占总能量的比例。自然照片通常是 20-30%,AI 图可能偏高或偏低。
log-log 斜率:功率谱在对数坐标系下的衰减斜率。自然图像符合 1/f² 规律,斜率约 -2.0。GAN 生成的图斜率往往更平坦,约 -1.0 到 -1.8。
两个特征加权组合,得出一个 FFT 评分。
这一块的挑战是性能。一张 1024x1024 的图要跑两轮 FFT,复杂度 O(N² log N)。我加了降采样到 512 的逻辑,实测一张图几十毫秒搞定。比在线 API 检测一次几秒钟还是快了不少。
SRM 噪声残差
这是 Fridrich & Kodovský 在 2012 年提出的方法,来源于图像取证领域,原本是用来检测隐写的。
核心是一个 5x5 的高通滤波器矩阵:

卷积之后,图像的内容被抑制,剩下的是像素级别的"噪声残差"。然后计算残差的标准差和峰度。
自然照片的残差标准差通常在 0.02-0.08,峰度在 0-5。AI 生成的图要么过于平滑(标准差偏低),要么有周期性伪影(峰度偏高)。
把这两个指标映射到 0-1 评分,就是它的贡献。
JPEG 量化表分析
最后一个是 JPEG 特有的。
JPEG 文件里有一个 DQT(Define Quantization Table)标记,记录压缩时用的量化表。ISO 标准规定了 luminance 和 chrominance 的推荐值。
扫描 JPEG 文件的所有 DQT 标记,跟标准表做对比。允许 15% 的偏差(毕竟不同的编码质量会有微调)。
结果分几种情况:
量化表跟标准完全偏离 → 可能来自 AI 工具的自定义编码 多组量化表 → 可能被二次压缩过,真实照片编辑后保存常见 标准表 → 中规中矩
这个信号处理起来不复杂,几十行代码就搞定了,实测下来如果不加这个检测 JPG 图片的 AIGC 识别水平会降一大块,就还挺有用的。
8 个信号不是平均分配的。
我设计了权重体系:C2PA 和 TC260 权重 100(绝对权重),SynthID 和 Camera 权重 40,ONNX 和 JPEG 权重 20,FFT 和噪声权重 10。最后加权平均得出最终评分。
但说实话,这个权重分配是拍脑袋定的。我没有足够的标注数据来训练最优权重,只能靠人工拉一些样图来调参。有人脸的照片、有人工痕迹的、不同压缩质量的——拉了几十张图,调了一版觉得差不多就先发布了。
如果后续有人提供更多的测试数据,权重可以再优化。或者哪天我懒癌好了,写个自动化调参脚本。
最后说点别的。
我写这个 detect 功能的初衷,不是要造一个完美的 AI 检测器。事实上,没有任何 AI 检测器是完美的。OpenAI 自己提供的检测工具,也只支持 C2PA 和 SynthID 两种信号,换了其他厂商的图就抓瞎。
我的想法是:多一种信号,就多一分判断依据。
8 个信号凑在一起,单个信号可能误判,但多数信号指向同一结论的时候,可靠性就上来了。这也是为什么我做了融合评分,而不是让用户看 8 个独立的数字。
目前视频检测还没写,等后面补上。视频的帧间一致性分析、时间域伪影检测,又是另一套算法,感兴趣的朋友可以关注后续。
今天就写这么多,试试你的 AI 图能不能检测出来,测完了来留言区说说结果。

夜雨聆风