
一位做图像处理的朋友找过我说他被一个看起来很简单的问题折磨了一下午图片用 OpenCV 读进来经过一些矩阵运算再交给 PIL 保存结果颜色变得像“红蓝互换”一样诡异。后来转成 Base64 字符串传给前端又发现图片加载不出来。这三个工具单个拎出来都很熟一旦串起来用就到处是暗坑。这篇文章我想把这几年在 OpenCV、PIL、Base64 之间来回倒腾图像数据的经验全部摊开讲清楚。这套组合的本质是一张图片从“像素矩阵”变成“内存对象”再变成“传输字符串”的完整生命周期。OpenCV 眼里的图像是一块 numpy 矩阵PIL 眼里的图像是一个带模式信息的 Image 对象Base64 眼里的图像只是一串没有感情的 ASCII 字符。矩阵要博弈是因为三个库对同一份数据的解读规则不同之所以叫量子化转换是因为每一步转换都在做“状态空间的离散映射”——图像矩阵被量化成有限的颜色层级二进制字节又被映射成 64 个可打印字符。这套链路适合所有做图像批处理、Web 上传预览、接口传输、数据库存储、自媒体封面批量生成的人也不管你是刚入门还是已经写过不少脚本理解清楚这条数据流能少踩 90% 的转换坑。1. “矩阵博弈”到底在争什么三剑客的定位与选型逻辑1.1 一个老问题图像数据在三个工具之间该怎么传先说个直观类比。OpenCV、PIL、Base64 像三个说着不同语言的人OpenCV 说“给我一个二维数组类型是 uint8三通道顺序是 BGR”PIL 说“给我一个RGB 模式的 Image 对象我才能正确保存和显示”Base64 则说“我不管你的图有多漂亮我只接收原始字节流而且要规整成 3 的倍数”。这三套规则单独看都不复杂边界恰恰出在“跨语言”的时候。最常见的翻车现场是这样先用cv2.imread把图片读成矩阵处理完当背景板拿给Image.fromarray想做点滤镜结果保存出来的 PNG 整张图像是开了“反相混色”特效。原因很简单OpenCV 读进来的通道顺序是 BGR而 PIL 默认按照 RGB 解释这三个通道。红色通道的值被当成蓝色去渲染颜色自然全乱了。我把这类问题统称为“矩阵博弈”同一堆 0 到 255 的数字在不同工具眼里被赋予了完全不同的物理意义。你要做转换就必须先搞清楚每个工具默认的矩阵布局、通道顺序、数组内存排布才能让数据无损地从一个世界迁徙到另一个世界。1.2 三者的核心分工与冲突点我自己在实际项目里的分工习惯是这样的工具擅长领域数据形态常见依赖角色OpenCV高性能计算、轮廓检测、几何变换、视频流numpy.ndarrayH x W x C默认 BGR矩阵计算主力PIL/Pillow格式转换、调色板量化、保存成各类图片、缩略图PIL.Image.Image有 mode 概念文件格式桥梁Base64网络传输、文本协议嵌入、防止二进制被中间层吞字符纯 ASCII 字符串传输与存储出口冲突点主要在两个地方。第一个是颜色通道的解释差异前面已经提到。第二个是数据的“厚重程度”OpenCV 爱操作大矩阵批量处理时内存占用高但速度快PIL 做小图处理很方便大图缩放得快但像素级操作不如 numpy 矩阵直观Base64 会把二进制体积再撑大约 33%如果原图没压缩直接编码传输成本会立刻暴涨。这三种工具从来不是替代关系而是上下游关系。OpenCV 负责“算”PIL 负责“换”Base64 负责“送”。想清楚这一点选型就不会纠结。1.3 我推荐的“枢纽”角色numpy 矩阵多套语言切换时最好有一个统一定义否则每转一次就可能丢一次信息。我的做法是凡是内部处理一律统一成 numpy 矩阵只有在需要保存成特定格式或者输出给网页时才转换成 PIL 对象或 Base64 字符串。这个思路能避免很多困惑。cv2.imread返回 numpy 数组PIL 的Image.fromarray也接受 numpy 数组所以numpy.ndarray就是自然的中转站。真正需要转换时只需要在边界处做一次通道顺序修正。另外矩阵的 dtype 也很重要OpenCV 的普通图像是uint8取值范围 0 到 255如果做浮点运算后直接转回不带 round 和 clip保存时会出现溢出和截断这类坑我已经见过太多次。提示把“图像处理”翻译成“矩阵处理”很多问题的思考难度立刻下降。不要被各种库的表面 API 带走多想想数组的 shape、dtype、通道数比死记函数名有用得多。2. 核心转换链路拆解OpenCV 与 PIL 的通道之争Base64 的字节游戏2.1 第一步OpenCV 读图后矩阵里到底存了什么当你执行img cv2.imread(photo.jpg)得到的变量是一个三维矩阵形状通常为(height, width, 3)灰度图则是二维。矩阵中的每个元素是像素点某一通道的亮度范围 0 到 255。第三个维度的顺序是 BGR不是 RGB。这意味着如果只把任意三通道矩阵传给 PIL 而不做转换渲染出来的颜色会完全对不上。有个技巧可以快速验证这个顺序找一张纯红色图片用 OpenCV 读进来查看img[0, 0]。红色区域在这个坐标下显示为(0, 0, 255)因为最后一个通道才是红。如果你从没注意过这点后面的颜色失真几乎是必然的。OpenCV 对矩阵布局还有一个影响它默认内存连续、行优先存储。np.ascontiguousarray这种操作有时候虽然不起眼但在调用很多底层图像库时非常关键。把 PIL 图像转成 numpy 数组后如果不确定内存连续性最好显式调用一次np.ascontiguousarray能避免某些 C 扩展层解析时出现的奇怪异常。2.2 第二步OpenCV 矩阵如何安全交给 PIL从 OpenCV 到 PIL 的安全姿势是先把 BGR 变成 RGB再创建 Image 对象import cv2 from PIL import Image cv_img cv2.imread(example.jpg) rgb_img cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) pil_img Image.fromarray(rgb_img)反过来从 PIL 交给 OpenCV 时也需要把 RGB 转回 BGRimport numpy as np rgb_array np.array(pil_img.convert(RGB)) bgr_array cv2.cvtColor(rgb_array, cv2.COLOR_RGB2BGR)这段代码没什么高深的地方但很多人会漏掉。做工具函数时我习惯把这两个动作封装成cv_to_pil和pil_to_cv这样业务代码里就不会频繁出现颜色反转。如果 PIL 图像带有 alpha 通道RGBA 模式转 numpy 后是四通道矩阵。OpenCV 大多数函数默认处理三通道或单通道图像四通道直接传进去经常会报错。这时要提前根据需求选择丢弃 alpha或者用cv2.cvtColor做 RGBA 到 BGRA 再拆通道。2.3 第三步Base64 编码不是魔法是 3 字节到 4 字符的映射Base64 的原理听起来很玄其实就是把任意的二进制字节流按照每 3 个字节一组每个字节 8 bit一共 24 bit再拆成 4 个 6 bit 的小块每个小块对应一个可打印 ASCII 字符。6 bit 最多表示 0 到 63正好对应 64 个字符所以叫 Base64。如果最后一组不够 3 字节就在末尾填充补齐。这也是为什么 Base64 之后的字符串长度一定比原字节数据多约三分之一。公式可以写成编码后字符数 4 * ceil(原始字节数 / 3)举个例子一张裁剪压缩后的 JPEG 图片原始字节是 80 KB。Base64 编码后大约会变成 106.7 KB如果通过GET参数拼在 URL 里很容易超出部分服务器或网关的长度限制。更糟糕的是编码结果中可能包含、/、这三个 URL 保留字符如果不做转义服务端解析时大概率会截断或者转义错误。所以我在写图像接口时如果不是必须用标准 Base64 传输会更倾向于用 URL-safe Base64也就是把标准字符表里的和/换成-和_再去掉末尾的。解码前再按长度补回来。Python 的base64.urlsafe_b64encode和base64.urlsafe_b64decode就是干这个的。2.4 矩阵存储里的字节序一个容易被忽略的隐藏规则提到 Base64 字节流很多只做应用层开发的人会觉得字节序离自己很远。但只要你经常把图像矩阵直接转成bytes就会意识到字节序和位序真实存在。cv2.imencode出来的字节流是按某种格式封装的 PNG/JPEG 字节串。PNG 文件内部又分成多个 chunk每个 chunk 都带着 CRC 校验、字节长度等信息大端小端都有约定。如果你在某个环节手滑做了字节倒序整个图片文件就会损坏。做单片机或者嵌入式 GUI 开发时“CAN 矩阵 字节序和位序”这类问题更常见因为图像数据经常以 RGB565、BGR565 等格式裸传每个像素被压缩成 2 个字节大小端稍微不一致屏幕上的颜色就会像打翻了调色盘。我曾经帮人排查一个嵌入式 GUI 问题屏幕显示图像整体偏色加错位。最后发现不是图像算法问题而是上位机把 RGB565 的高字节和低字节发送顺序搞反了。这类问题用 OpenCV 的矩阵思维很难直接看出必须脱离图像库把像素当纯字节流来看。矩阵博弈的背后其实还藏着“字节序博弈”。3. 实战管道从图像矩阵处理到量子化压缩再到 Base64 交付3.1 场景定义批量封面图处理假设有个需求业务方要给一批商品图统一加角标同时控制文件体积最后把图片转成 Base64 字符串交给某个外部接口。输入是乱七八糟的 JPG、PNG输出要求是正方形缩略图、体积不超过 100 KB、编码后能直接嵌进 JSON 字段。这个需求用“三剑客”非常合适。OpenCV 负责读取和几何处理PIL 负责调色板量化和格式转换Base64 负责最终交付。3.2 完整代码三剑客协同 workflow下面是我实际项目中抽出来的一个核心处理管道注释尽量写得直白import base64 import cv2 import numpy as np from PIL import Image from io import BytesIO def process_image_to_base64(input_path: str, target_size: int 512, quality: int 85): # 1. OpenCV 读图统一为矩阵 cv_img cv2.imread(input_path) if cv_img is None: raise ValueError(图片读取失败检查路径或文件是否损坏) # 2. 居中裁剪成正方形直接 resize 会变形 h, w cv_img.shape[:2] side min(h, w) start_y (h - side) // 2 start_x (w - side) // 2 cropped cv_img[start_y:start_y side, start_x:start_x side] # 3. 缩放并转 RGB交给 PIL 处理保存格式 resized cv2.resize(cropped, (target_size, target_size), interpolationcv2.INTER_AREA) rgb_img cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) pil_img Image.fromarray(rgb_img) # 4. 用 PIL 做量化压缩这里用 256 色调色板适合带渐变和写实的图 # 若原图是卡通、Logo 类颜色较少的图colors 可以降到 64 或 32 quantized pil_img.convert(P, paletteImage.ADAPTIVE, colors256) # 5. 转成 RGB 再保存为 JPEG因为普通 JPEG 不支持调色板索引模式 save_img quantized.convert(RGB) buffer BytesIO() save_img.save(buffer, formatJPEG, qualityquality, optimizeTrue) data_bytes buffer.getvalue() # 6. Base64 编码并转回字符串 b64_str base64.b64encode(data_bytes).decode(ascii) return b64_str result process_image_to_base64(input.jpg, target_size512, quality85) print(len(result))这段代码里第 3 步的cv2.resize用了INTER_AREA插值这个经验来自实测缩小图片时INTER_AREA能避免明显摩尔纹比默认的线性插值干净不少。第 4 步的convert(P, paletteImage.ADAPTIVE, colors256)本质上就是图像量化把真彩色空间中每个像素可能的上百万种颜色收敛到 256 个有代表性的颜色上。这个动作减少了颜色状态数量是肉眼难察觉的降维操作。3.3 量子化压缩的参数怎么定很多人看“量子化”这个词觉得玄其实就是量化。图像量化就是把连续的色彩空间离散成有限个层级。常见的量化方式有三种第一是灰度级量化。比如把 256 级灰度压成 16 级公式很简单new_pixel old_pixel // 16 * 16。图像会呈现明显的色带和断层常见于复古风格处理。第二是调色板量化。PIL 的convert(P, paletteImage.ADAPTIVE, colorsn)会把全图颜色聚类成 n 种然后每个像素只保存一个“调色板索引”。这样图片就能以索引色模式存储文件体积比真彩 PNG 小很多。需要注意索引色模式下很多滤镜不能直接用保存前往往要转回 RGB。第三是色彩深度量化。RGB888 降到 RGB565 就是典型每个通道从 8 bit 降到不同 bit 数。这种量化最贴近“位深度”这个说法做单片机图像传输时很常用代价是有轻微偏色。参数怎么定取决于图像内容。写实照片用 256 色调色板基本无损观感扁平插画和 Logo 用 32 色甚至 16 色也没问题有大量渐变的天空图若强行降到 32 色会出现肉眼可见的色带。实际操作时我会写个循环分别用 256、128、64 色跑一遍比较输出文件大小和视觉差异三分钟内能出一个最适合业务的选择。3.4 体积与画质平衡实测我拿一张 1600 x 1200 的样张做过对比记录如下处理方式输出大小视觉感受原始 PNG 转 Base64约 1.1 MB细节完整直接转 JPEG quality90约 260 KB几乎无损先裁剪到 512x512 再 JPEG quality85约 78 KB正常缩略图观感先量化 256 色再 JPEG quality85约 68 KB观感略柔和几乎无差量化 64 色再 JPEG quality80约 45 KB渐变区域可见轻微条纹结论很清晰如果你的目标是“Base64 字符串尽量短”最先要做的不是纠结编码方式而是把源图尺寸降下来再用像素量化压缩色彩数。Base64 永远会在原始字节数上增加 33% 左右的开销这个账怎么都逃不掉你必须在进入编码前就把原始数据压缩到位。提示对 JPEG 来说真正决定体积的是 DCT 变换后的量化表参数也就是quality。WebP 在低码率下往往比 JPEG 表现更好但如果外部接口不支持 WebP就别自找麻烦老老实实 JPEG。4. 传输与存储场景里的 Base64外链拼接、数据库字段、前端 data URI4.1 为什么 Base64 会丢参数微信外链拼接和 URL 转义问题很多人的体验是把 Base64 字符串拼到 URL 参数里电脑浏览没问题同事发到微信里点开就挂。原因多半是标准 Base64 里的在 URL query 里被解析成空格/会被当成路径分隔符又经常和某些框架的参数分隔规则冲突。假如你用一个图片处理服务外链地址形如https://example.com/render?dataxxxxxyyyy/zzzz点击后服务端解码时可能只拿到了xxxxx yyyy剩下的内容因为/把路径截断或者触发了参数校验规则直接丢失。一旦 Base64 字符串不完整图片数据必然损坏接口返回 400 或者乱码。解决办法分两种。如果你控制的是自己后端接口最简单是用 URL-safe Base64替换字符后去掉等号同时拼接前务必先调用encodeURIComponent对整段参数做一次 URL 编码这是最保险的姿势。如果你只能传标准 Base64 且无法改协议那就不要把它拼在 query 里改用 POST 放在请求体里传从源头避开 URL 保留字问题。4.2 编码前压缩还是要编码后压缩有一个经常被问起的问题Base64 字符串还能不能进一步压缩标准 Base64 的字符集是固定的 64 个字符每个字符在底层用 8 bit 传输但理论上一个字符只需要 6 bit。所以 Base64 形式的数据天然比二进制多占约 33% 的传输体积这是协议层面的冗余没法通过“再编码一次”这件事消除。因此策略应该前置。先控制分辨率、压缩 quality、量化色彩数再对最终字节做 Base64。如果一张图从 1 MB 压缩到 80 KB多出来的 Base64 开销是 27 KB如果拿着 1 MB 去编码再压缩字符串通常也压不回 80 KB因为 Base64 字符分布相对均匀通用压缩算法收益有限。我曾遇到有人为了减小 JSON 字段体积把 Base64 字符串又做了一层 gzip。服务器收到后再解压最后还要 Base64 解码。耗时高、代码复杂省下来的空间通常不到 10%在绝大多数业务里是不划算的。4.3 多层嵌套解码能用但别滥用搜索引擎里时不时有人搜“base64 多层嵌套解码”网上也会有一些连续解码直到看见明文的小工具。从技术实现上讲只要编码时逐层调用b64encode解码时逐层调用b64decode就能完整还原。但多层嵌套本身并不能带来加密强度它只是把数据包了一层又一层的壳纯属对可读性的混淆。恶意软件常利用多层 Base64 嵌套藏 payload这也是很多风控系统会对多层嵌套参数高度警惕的原因。开发时别为了“显得复杂”去嵌套 Base64它既不安全又会让排查问题的人血压升高。如果真有加密需求应该使用正规的加密算法而不是靠编码层数伪装。4.4 数据表与缓存里的存储策略把图片以 Base64 形式直接存进数据库字段是很多小型项目的起步做法。这样做最直观前端拿到字符串就能嵌进img srcdata:image/jpeg;base64,...。但表数据量一旦上来字段膨胀、查询变慢、备份变大这些麻烦接踵而至。我的建议是小图标、配置图片、临时预览图可以存 Base64但超过 50 KB 的图片尽量落对象存储或本地文件数据库里只存路径和图片元信息。真需要在内网接口里传图时Base64 定位成短生命周期通道而不是长期存储格式。如果非要存 Base64字段统一用TEXT类型避免 MySQL 旧版本对BLOB的默认临时表处理影响性能。接口返回时尽量保留 data URI 的 MIME 头否则部分前端组件无法直接识别纯 Base64 字符串还要额外拼接前缀。5. 高频踩坑与排查实录从安装到转换的速查表5.1 颜色反转BGR/RGB 的经典事故开头提到的颜色反转问题是所有“三剑客”链路里出现频率最高的。OpenCV 读入后矩阵通道顺序为 BGR且不少教程没有说明很多人直接Image.fromarray(cv_img)保存一看红蓝对调。记住这条铁律OpenCV 和 PIL 之间转换十次里有九次要先做通道顺序转换反过来也一样。# OpenCV 转 PIL rgb cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) pil Image.fromarray(rgb) # PIL 转 OpenCV rgb np.array(pil.convert(RGB)) bgr cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)5.2 cv2.imencode 的扩展名与输出缓冲问题用cv2.imencode编码图片时第一个参数写的扩展名直接决定编码器类型。写.jpg和写.png得到的数据量和像素还原度都不同别为了省事随便填空。如果写.jpg后还希望控制压缩比需要传第二个参数ok, encoded cv2.imencode(.jpg, img, [int(cv2.IMWRITE_JPEG_QUALITY), 85])确保ok为 True再对encoded.tobytes()做 Base64。如果ok为 False大概率是矩阵为空、dtype 不对或尺寸异常。从 Base64 解码回图像时常见错误是cv2.imdecode输入的数组不是一维 uint8。稳妥写法是raw_bytes base64.b64decode(b64_str) np_arr np.frombuffer(raw_bytes, dtypenp.uint8) img cv2.imdecode(np_arr, cv2.IMREAD_COLOR)若担心内存引用问题可以再加np.asarray(np_arr)避免某些场景下frombuffer返回只读数组导致后续写入报错。5.3 OpenCV 安装与导入ModuleNotFoundError 的真相搜“opencv 安装教程”的人极多最常见报错是ModuleNotFoundError: No module named opencv。直白讲Python 环境中 import 的名字是cv2不是opencv所以安装时包名叫opencv-python导入时却必须写import cv2。如果安了opencv-contrib-python能额外获得 SIFT、SURF 这些带专利或高级功能的模块基础使用装普通版就够了。在 Anaconda 环境里我常用conda install -c conda-forge opencv因为 conda-forge 的包通常能自动匹配 numpy 等底层依赖少一些 pip 和 conda 混用导致的版本冲突。如果项目已经用了较高版本的 CUDA 想启用 GPU 加速需要从源码编译带 CUDA 的 OpenCV或者找预编译的 wheel这个操作比较折腾对普通图像处理和 Base64 转换场景没必要。5.4 中文路径与特殊字符路径另一个很实际的坑cv2.imread遇到中文路径时在 Windows 上经常返回 None因为内部调用的是不支持中文编码的 C 函数。换用cv2.imdecode可以解决import numpy as np import cv2 data np.fromfile(测试图片/封面.jpg, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR)写文件用cv2.imencode配合tofile保存也能规避中文路径问题。PIL 的中文路径支持相对好一些但底层在部分环境也会踩编码坑所以我的习惯是统一用字节流读写减少跨平台差异。5.5 常见问题速查表现象原因解决OpenCV 读的图PIL 保存后红蓝互换BGR/RGB 通道顺序未转换用cv2.cvtColor做 BRG2RGB别省这行Base64 放进 URL 参数后图片损坏变空格、/截断路径、干扰解析URL-safe Base64 或encodeURIComponentcv2.imdecode报错输入数组不是一维 uint8用np.frombuffer再转uint8中文路径读图返回 NoneOpenCV 读取函数编码兼容问题改用np.fromfileimdecodePIL 量化后图片出现明显条纹调色板颜色数太少或原图有平滑渐变提高 colors 数值或用误差扩散再量化处理Base64 体积太大原始图片尺寸和压缩质量没控制先缩放、先量化再编码大量图片用 Base64 存数据库文本字段膨胀严重对象存储存文件库中只存路径6. 把“矩阵论”玩成图像实验这个组合还能做什么6.1 用图像操作直观理解线性代数“矩阵博弈”不是只存在于 OpenCV 的像素处理中。矩阵论的很多概念都可以用图像实验直接看到结果。例如图像的平移、旋转、缩放其实就是对像素坐标做一个仿射变换把变换矩阵的几个参数改一改你就能看到图片在画布上发生对应运动。import cv2 import numpy as np img cv2.imread(input.jpg) h, w img.shape[:2] # 绕中心旋转 30 度 M cv2.getRotationMatrix2D((w / 2, h / 2), 30, 1.0) rotated cv2.warpAffine(img, M, (w, h)) # 水平错切理解非对角线元素的作用 M_shear np.float32([[1, 0.3, 0], [0, 1, 0]]) sheared cv2.warpAffine(img, M_shear, (int(w * 1.3), h))看代码可能没感觉但跑一遍就能发现旋转矩阵里那几个三角函数值决定的是每个像素跑到哪错切矩阵里的非对角元素决定的是图像是否发生倾斜。这种直观体验比在纸上写十页矩阵推导更能建立“矩阵即变换”的直觉。6.2 用混淆矩阵理解图像分类结果图像处理经常和机器学习配套使用。训练一个分类模型识别图片时光看准确率很难发现模型到底错在哪类图上。这时候会用到混淆矩阵横轴是预测类别纵轴是真实类别对角线越亮说明正确率越高非对角线出现亮点就说明某两个类别容易被相互混淆。OpenCV 可以作为数据读取端把批量样本从磁盘送入模型再把推理结果收集起来最后一行代码就能画出彩色混淆矩阵热力图。图像处理博主常说“让矩阵可视化”这一步就是典型例子。当你把一张 3x3 或 10x10 的混淆矩阵渲染成色块图哪里分类不好一眼就能看到。6.3 从 CAN 矩阵聊到字节序与位序搜热词时经常会看到“CAN 矩阵字节序和位序详细解析”这个思路其实和图像字节转换有些相通。CAN 总线报文里一个 16 bit 或 32 bit 的信号可以被拆成不同字节位置和位位置发送端和接收端如果对字节序、位序的理解不一致解析出的物理量就会错误。图像裸数据发送也有类似逻辑RGB565 像素的每个分量拆到两个字节中若发送端用大端组装、接收端用小端解析颜色就会错乱。这类问题表面上和 OpenCV、PIL、Base64 没有直接关系但背后的思维模型是通用的任何数据在传输和存储时都要先约定“字节排列规则”否则交换双方就会互相误解。Base64 之所以能成为网络传输里的“通用语言”恰恰是因为它把不稳定的二进制字节流变成稳定安全的 ASCII 字符串从编码层面解决了很多传输层的兼容问题。6.4 这套组合还能扩展出什么顺着这条链路还可以继续玩很多方向。比如加一层 OCR用 OpenCV 做预处理后把识别文本和图片一起编码成 JSON 交付加一层哈希校验在 Base64 字符串末尾附带摘要接收端校验完整性加一个队列工具批量处理自媒体矩阵账号需要的封面图、广告图、头像图统一输出固定尺寸和压缩率。有一点我要提醒自动化管道里暴露的图片接口返回的 Base64 参数最好都做长度限制和 MIME 白名单校验。不然一个恶意用户传超大图片后端可能被拖垮。风控不是只有账号体系才需要图像入口同样需要。