尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零实现EU AI内容标签:图片视频打标实战指南

从零实现EU AI内容标签:图片视频打标实战指南 最近在 Hacker News 上看到“Show HN: Add the official EU AI-content labels to images and videos”这个项目第一反应是这正好踩在了 AI 内容治理的刚需上。欧盟《人工智能法案》落地后AI 生成的图片、视频、音频在发布时不再只是“建议标注”而是需要带有人类可读的可见标识以及机器可读的元数据标记。很多开发者的痛点是没有一套能直接嵌入媒体处理流水线的实现方案。这篇文章就围绕这个主题展开我会从概念、设计、选型、代码、踩坑五个层面带大家从零实现一个“给图片和视频添加 EU AI 内容标签”的本地命令行工具。文章面向的内容创作者、后端开发者、AI 应用开发者都适用读完你可以直接把这套逻辑迁移到自己的内容发布系统里。1. 背景与核心概念1.1 什么是 EU AI-content labelsEU AI-content labels 指的是根据欧盟《人工智能法案》透明度义务对 AI 生成或深度合成内容施加的标识机制。简单理解就是如果一个图片或视频是 AI 生成的或者里面包含 AI 合成的内容那么在对外发布时就要让人一眼能识别出“这不是真实拍摄的常规内容”。这个标识可以是画面角落的徽标、水印也可以是嵌入文件里的元数据甚至在专业场景下两者都要有。为什么要这么做因为 AI 生成内容越来越逼真用户无法靠肉眼判断视频和图片真假。如果平台不对合成内容做标识很容易引发虚假信息传播、版权纠纷、肖像侵权等问题。欧盟法案把这种“告知义务”写进了合规要求面向的是提供 AI 系统的企业以及使用 AI 内容进行公开传播的实体。在技术实现上EU AI-content labels 通常拆成两个维度维度形式目的典型实现可见标识画面叠加的文字、图标、徽标让人能直接看到PIL 绘制水印、ffmpeg drawtext机器可读标识EXIF、XMP、C2PA 等元数据让平台和软件能自动识别exiftool、C2PA SDK这两个维度不是二选一而是应该一起做。只加可见水印元数据缺失平台无法自动分类只写元数据普通用户看不到透明度义务仍然没有完全履行。1.2 这套能力解决什么问题在很多实际项目中AI 生成内容的产出链路很完整但“发布前打标”这个环节经常被遗漏。比如企业内部有一个 AIGC 营销图片生成系统模型每天都在产出大量商品图、海报图。系统把这些图直接推到内容管理平台再由运营下载发布到社媒。问题就在于图片生成时是否写入了 AI 标签导出后标签会不会被压缩清除发布到第三方平台后平台能否识别内容来源如果从一开始就在媒体处理管线中加上“EU AI-content labels”这一步这些问题就可以标准化解决。项目标题里提到“Add the official EU AI-content labels to images and videos”本质上就是在找一条标准化路径把标识动作变成一条可复用、可批量执行的处理流程。1.3 对开发者的意义掌握 AI 内容标签技术不只是为了合规更是为了后续系统设计。内容平台需要根据标签对 AI 内容做差异化展示。广告审核系统需要识别合成内容判断是否符合平台规则。数字资产管理系统需要保留内容来源信息。模型训练数据治理需要区分真实数据和合成数据。所以理解“可见标签 元数据标签”这套组合方案对做内容产品、AI 应用、数据治理的同学来说都是加分项。2. 实现思路与技术选型2.1 整体流程设计我们要实现的工具输入一张图片或一个视频输出一份打上标签后的新文件。整体流程可以拆成四步读取媒体文件 - 采样/定位标签区域 - 叠加可见标签 - 写入机器可读元数据 - 输出新文件对图片来说核心操作是在像素层做合成。对视频来说有两种实现路线把标签作为固定元素叠加到每一帧画面。只在视频开头或指定时间区间显示标签。从透明度义务角度出发AI 合成内容的标签最好全程可见。如果视频较长也可以采用“片头显示 元数据标记”的折中方案但工程上要提前和业务方约定标准。2.2 技术栈选择既然要同时处理图片和视频我建议用 Python 做整体胶水层核心处理交给成熟的媒体库。模块作用说明Pillow图片读取、绘制、合成轻量适合叠加标签OpenCV视频逐帧处理灵活可精确控制水印位置ffmpeg视频元数据、编码、时间轴处理生产环境性能更好exiftool写入和读取 EXIF/XMP 元数据跨格式统一标签字段全argparse命令行接口后续可以替换成 Click 或 Typer选这套组合的原因是Pillow 对图片的常见格式支持好PNG、JPEG、WebP 都能处理。OpenCV 处理视频方便但重新编码视频会有画质损失所以生产环境优先 ffmpeg。exiftool 能处理图片、视频、PDF 等多种格式的元数据写标签时不用为每种格式单独开发。2.3 为什么不用单一工具搞定所有事很多人会问ffmpeg 不是也能给图片加水印、写元数据吗为什么还要写 Python 脚本ffmpeg 确实能做但它更擅长批处理不适合做复杂业务逻辑。比如需要根据图片内容自动计算标签位置。需要从数据库读取 AI 生成记录并动态生成标签文本。需要同时写多个厂商自定义的元数据字段。这些业务逻辑用 Python 写更自然。ffmpeg 可以负责重编码Pillow/OpenCV 负责画面合成exiftool 负责元数据写入各司其职。3. 环境准备与版本说明本文示例以 Python 3.9 及以上版本为主。不同系统下的安装命令略有差异下面是通用安装流程。3.1 Python 虚拟环境建议创建一个新的虚拟环境避免依赖冲突。python3 -m venv eu-ai-label-env source eu-ai-label-env/bin/activateWindows 下激活命令是eu-ai-label-env\Scripts\activate3.2 安装 Python 依赖创建requirements.txtPillow10.0.0 opencv-python4.8.0安装依赖pip install -r requirements.txt这里要注意opencv-python 的安装包比较大如果只需要视频处理也可以只安装 Pillow视频部分用 ffmpeg 命令行替代。3.3 安装 ffmpegffmpeg 主要负责视频编码和元数据处理。如果你只处理图片可以跳过这一步。macOSbrew install ffmpegUbuntu/Debiansudo apt update sudo apt install ffmpegWindows 可以通过 winget 安装winget install ffmpeg验证ffmpeg -version3.4 安装 exiftoolexiftool 是 Perl 写的元数据工具支持格式很广。macOSbrew install exiftoolUbuntu/Debiansudo apt install libimage-exiftool-perlWindows 可以到 exiftool 官网下载压缩包或者使用 wingetwinget install exiftool验证exiftool -ver版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。4. 核心实现拆解4.1 设计标签素材欧盟对官方 AI 内容标签的视觉符号有正式设计规范具体图标素材和颜色值建议以官方发布为准。我们在代码里先定义一个可配置的标签模版后续替换成官方素材即可。为了避免版权或规范风险示例代码使用文字 圆角矩形作为占位标签结构如下--------------------- | AI generated | | content | ---------------------这个标签模版可以通过配置项调整文字内容、字体大小、背景色、透明度。4.2 图片标签核心代码先用 Pillow 实现一个“标签图片生成函数”。# 文件路径utils/label_image.py from PIL import Image, ImageDraw, ImageFont def create_label_image(text, width260, height72): 生成一个 RGBA 标签图片 text: 标签文本例如 AI generated content label Image.new(RGBA, (width, height), (0, 0, 0, 0)) draw ImageDraw.Draw(label) # 画圆角背景 draw.rounded_rectangle( [0, 0, width - 1, height - 1], radius12, fill(30, 70, 140, 220), outline(255, 255, 255, 200), width2, ) # 绘制文字 font_size 28 try: font ImageFont.truetype(Arial.ttf, font_size) except IOError: font ImageFont.load_default() bbox draw.textbbox((0, 0), text, fontfont) text_width bbox[2] - bbox[0] text_height bbox[3] - bbox[1] x (width - text_width) / 2 y (height - text_height) / 2 draw.text((x, y), text, fontfont, fill(255, 255, 255, 255)) return label解释几个关键点Image.new(RGBA, ...)创建透明画布这样叠加到原图时不会出现黑底。draw.rounded_rectangle绘制圆角矩形视觉上更接近官方徽标的感觉。textbbox用于计算文字居中位置避免不同字体下文字偏移。字体找不到时回退到默认字体保证最小可用性。接下来是“给图片叠加标签”的函数。# 文件路径utils/label_image.py def add_label_to_image(input_path, output_path, textAI generated content, positiontop-left, padding24): 给输入图片叠加 AI 内容标签 position: top-left / top-right / bottom-left / bottom-right image Image.open(input_path).convert(RGBA) label create_label_image(text) margin_x padding margin_y padding if position top-left: x, y margin_x, margin_y elif position top-right: x image.width - label.width - margin_x y margin_y elif position bottom-left: x margin_x y image.height - label.height - margin_y elif position bottom-right: x image.width - label.width - margin_x y image.height - label.height - margin_y else: raise ValueError(fUnsupported position: {position}) # 把标签合成到图片左上角 image.alpha_composite(label, (x, y)) # 转回 RGB 保存避免 JPEG 不支持 RGBA output image.convert(RGB) output.save(output_path, quality95)这里有个常见误区JPEG 格式不支持 RGBA 透明通道所以在保存 JPEG 之前必须转成 RGB否则保存时会被强制转换甚至可能报错。测试一下python -c from utils.label_image import add_label_to_image add_label_to_image(samples/sample.jpg, outputs/sample_labeled.jpg) 运行后在outputs目录下会生成一张增加了标签的新图片。4.3 视频标签核心代码视频处理比图片复杂因为要处理帧的概念。最直接的方法是使用 OpenCV 逐帧读取、叠加、写入。# 文件路径utils/label_video.py import cv2 from utils.label_image import create_label_image def add_label_to_video(input_path, output_path, textAI generated content, positiontop-left, padding24): 使用 OpenCV 给视频每一帧叠加 AI 内容标签 cap cv2.VideoCapture(input_path) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 创建标签并缩放到视频宽度的一定比例 label create_label_image(text) scale max(0.3, min(0.5, width / 1280)) label_width int(label.width * scale) label_height int(label.height * scale) label_resized cv2.cvtColor(cv2.resize( cv2.cvtColor(label, cv2.COLOR_RGBA2BGRA), (label_width, label_height) ), cv2.COLOR_BGRA2RGBA) # 视频写入器 fourcc cv2.VideoWriter_fourcc(*mp4v) writer cv2.VideoWriter(output_path, fourcc, fps, (width, height)) frame_index 0 while True: ret, frame cap.read() if not ret: break # 把标签合成到当前帧 if position top-left: x, y padding, padding elif position top-right: x width - label_width - padding y padding elif position bottom-left: x padding y height - label_height - padding elif position bottom-right: x width - label_width - padding y height - label_height - padding else: x, y padding, padding # 使用 alpha 通道合成 overlay label_resized alpha overlay[:, :, 3] / 255.0 roi frame[y:y label_height, x:x label_width] for c in range(3): roi[:, :, c] (1 - alpha) * roi[:, :, c] alpha * overlay[:, :, c] frame[y:y label_height, x:x label_width] roi writer.write(frame) frame_index 1 if frame_index % 30 0: print(fprocessed {frame_index} frames) cap.release() writer.release() print(fdone, total {frame_index} frames)这段代码可能让人觉得“有点复杂”因为 OpenCV 的彩色通道顺序是 BGR而 Pillow 是 RGBA两者转换时容易出问题。我把缩略标签从 RGBA 转成了 BGRA 又转回 RGBA就是为了让通道对应关系清晰。如果你不想写这么复杂的逐帧逻辑可以使用 ffmpeg 的 drawtext 滤镜对视频做水印叠加速度快很多。ffmpeg -i input.mp4 \ -vf drawtextfontfile/System/Library/Fonts/Helvetica.ttc:textAI generated content:fontcolorwhite0.8:fontsize28:box1:boxcolorblack0.5:boxborderw12:x24:y24 \ -codec:a copy output.mp4说明fontfile需要指定一个真实字体路径系统不同路径不同。fontcolorwhite0.8表示白色、透明度 0.8。box1表示给文字加背景框提升可读性。-codec:a copy表示音频不重新编码节省时间。ffmpeg 方案适合大批量处理OpenCV 方案适合需要动态计算标签位置的业务场景。4.4 写入机器可读元数据视觉标签只是其中一部分。为了让平台能自动识别内容性质还需要写入元数据。这里我们使用 exiftool。写一个 Python 封装函数# 文件路径utils/metadata.py import subprocess def add_ai_metadata(file_path, descriptionAI-generated content): 使用 exiftool 写入 AI 内容描述元数据 cmd [ exiftool, f-ImageDescription{description}, f-XMP-dc:Description{description}, -overwrite_original, file_path, ] subprocess.run(cmd, checkTrue)这里使用ImageDescription和XMP-dc:Description是相对通用的字段兼容 JPEG、PNG、MP4。官方标准可能对特定字段有要求你可以根据合规需求替换字段名。验证是否写成功exiftool -ImageDescription -XMP-dc:Description output.jpg输出类似Image Description : AI-generated content Description : AI-generated content4.5 组合成完整 CLI 工具把上面的逻辑整合到一个命令行工具中。# 文件路径eu_ai_label.py import argparse from pathlib import Path from utils.label_image import add_label_to_image from utils.label_video import add_label_to_video from utils.metadata import add_ai_metadata def main(): parser argparse.ArgumentParser( descriptionAdd EU AI-content labels to images and videos ) parser.add_argument(input, typestr, helpinput image or video path) parser.add_argument(output, typestr, helpoutput path) parser.add_argument(--text, defaultAI generated content, helplabel text) parser.add_argument(--position, defaulttop-left, choices[top-left, top-right, bottom-left, bottom-right]) parser.add_argument(--no-metadata, actionstore_true, helpskip writing metadata) parser.add_argument(--engine, defaultauto, choices[auto, image, video]) args parser.parse_args() input_path Path(args.input) output_path Path(args.output) output_path.parent.mkdir(parentsTrue, exist_okTrue) suffix input_path.suffix.lower() image_ext {.jpg, .jpeg, .png, .webp, .bmp} video_ext {.mp4, .mov, .avi, .mkv} engine args.engine if engine auto: if suffix in image_ext: engine image elif suffix in video_ext: engine video else: raise ValueError(funsupported file type: {suffix}) if engine image: add_label_to_image(str(input_path), str(output_path), textargs.text, positionargs.position) elif engine video: add_label_to_video(str(input_path), str(output_path), textargs.text, positionargs.position) if not args.no_metadata: add_ai_metadata(str(output_path)) print(fdone: {output_path}) if __name__ __main__: main()使用方法python eu_ai_label.py input.jpg output.jpg python eu_ai_label.py input.mp4 output.mp4 --position bottom-right这样一个基础但完整的“EU AI-content labels”工具就完成了。5. 完整实战案例为了更贴近真实场景我们做一个批量处理示例。假设你有一个input_dir里面全是素材图片和视频需要统一打标并输出到output_dir。5.1 准备测试文件创建目录mkdir -p samples inputs outputs把测试图片放到inputs目录。5.2 批量处理脚本新建batch_process.py# 文件路径batch_process.py import sys from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed from eu_ai_label import main as label_main INPUT_DIR Path(inputs) OUTPUT_DIR Path(outputs) SUPPORTED_IMAGE {.jpg, .jpeg, .png, .webp, .bmp} SUPPORTED_VIDEO {.mp4, .mov, .avi, .mkv} def process_one_file(file_path: Path): output_path OUTPUT_DIR / file_path.name cmd [ sys.executable, eu_ai_label.py, str(file_path), str(output_path), --position, bottom-right, ] import subprocess subprocess.run(cmd, checkTrue) return file_path.name def main(): OUTPUT_DIR.mkdir(exist_okTrue) files [p for p in INPUT_DIR.iterdir() if p.suffix.lower() in SUPPORTED_IMAGE | SUPPORTED_VIDEO] with ThreadPoolExecutor(max_workers4) as executor: futures [executor.submit(process_one_file, f) for f in files] for future in as_completed(futures): name future.result() print(ffinished: {name}) if __name__ __main__: main()运行python batch_process.py这里用线程池并行执行是因为大部分时间花在图片编码和视频编码上多进程并发可以显著提升吞吐。5.3 验证输出检查一张图片的元数据exiftool outputs/sample.jpg检查视频流是否包含标签画面可以用 ffmpeg 抽一帧来看ffmpeg -i outputs/sample.mp4 -ss 00:00:01 -vframes 1 output_frame.png打开output_frame.png确认画面右下角有标签。6. 常见问题与排查思路我在实际写这套流程时遇到最多的问题集中在字体、通道、元数据丢失三个方面。问题现象常见原因解决思路Pillow 绘制中文或特殊字符报错字体文件不支持字符更换支持对应字符的字体文件保存 JPEG 后标签背景变成黑色JPEG 不支持透明通道保存前用 convert(RGB) 去掉透明通道OpenCV 叠加后颜色异常RGB/RGBA/BGR 通道顺序不一致统一转成 BGRA 或 RGBA并做 alpha 合成ffmpeg drawtext 提示字体不存在字体路径错误或系统没有该字体用 fc-list 或明确字体绝对路径加水印后视频音画不同步时间基准被破坏使用-codec:a copy或重新设置-fps_mode passthrough平台显示元数据丢失平台压缩存储时删除了元数据在发布前再次调用打标接口保留原始文件exiftool 提示字段不存在目标文件类型不支持该字段改用-Comment或-Description通用字段6.1 ffmpeg drawtext 字体问题排查Linux 环境下可以先查看系统字体fc-list :langzh找到你想要的字体文件路径再传给fontfile。6.2 OpenCV 合成时标签半透明效果淡如果标签过淡可以把 alpha 权重提高。比如强制 alpha 为 0.9alpha 0.9注意这会降低画面的可读性需要在醒目和美观之间做取舍。6.3 元数据写入失败如果 exiftool 因为权限或只读文件报错先确认文件不是只读chmod uw output.jpg如果文件是 PNG某些字段可能不支持优先使用XMP-dc:Description。7. 最佳实践与工程建议7.1 标签内容保持统一公司内部要定义统一的标签文本、字体、颜色、位置不要每个项目自己发明一套。建议把配置抽成 yaml 文件。# config.yaml label: text: AI generated content position: bottom-right padding: 24 font_size: 28 background_color: [30, 70, 140, 220] text_color: [255, 255, 255, 255] metadata: description: AI-generated content author: content-platform written_by: eu-ai-labeler7.2 保留原始文件输出新文件不要在原始素材上直接覆盖写标签。原始文件是内容资产标签可能因为发布渠道不同需要调整。建议流程原文件 - 打标 - 校验 - 发布文件 - 归档7.3 元数据要写多个字段不同的平台读取元数据的方式不同。有的读 XMP有的读 EXIF有的读 QuickTime 标签。建议至少同时写入ImageDescription和XMP-dc:Description。如果合规要求使用 C2PA 标准还需要引入 C2PA 签名工具。7.4 大规模处理时先做小样验证不要直接对几千个文件跑批量脚本。先拿两张图片、一个小视频测试确认标签位置正确。文字没有溢出。画质损失在可接受范围。元数据能被目标平台读出。7.5 打标是内容治理的一部分从工程角度说标签不是加一个水印就结束了。内容制作系统、内容审核系统、数据中台都应该识别这个标签。建议在数据结构里为每个素材增加ai_label_status字段打标流程完成后回写状态。ai_label_status: pending - labeled - verified8. 后续可以继续做的事到这里我们已经完成了一个可用的 EU AI-content label 本地工具。它能在图片和视频上叠加可见标签并写入基础元数据。但离生产级还有一段距离有几个方向可以继续完善。8.1 接入 C2PA 标准C2PA 是内容来源与真实性的开放标准可以提供防篡改的来源信息。如果你的内容会广泛传播建议引入 C2PA 签名具体可以使用官方 SDK 或c2patool命令行工具。8.2 服务化改造把打标逻辑封装成 HTTP 服务供内容平台统一调用。POST /v1/ai-label Content-Type: multipart/form-data file: input.jpg这样平台内部各系统都能复用同一套打标能力而不是每个业务团队各自实现。8.3 标签校验工具增加一个“校验模式”读取指定文件判断是否包含 AI 内容标签python eu_ai_label.py check output.jpg如果标签存在返回元数据和可见标签位置如果不存在返回警告码。这样发布流程里可以自动拦截未打标内容。这个项目虽然从“欧盟 AI 内容标签”切入但本质上是在解决 AI 内容可信传播的工程问题。建议你先把图片路径跑通再尝试视频路径最后根据自己的业务形态调整元数据方案。如果你在实现过程中也遇到了其他坑欢迎在评论区一起讨论。
返回列表