116-Python调用GPT-4V分析图片-Base64编码-多图-JSON结构化输出

文章目录

  • 【116.Python+AI】用Python调用GPT-4V分析图片:一句话让它描述、分类、检测你的图像
    • 导入语
    • [1 ~> Hello World:30行看图说话](#1 ~> Hello World:30行看图说话)
      • [1.1 调用链路](#1.1 调用链路)
    • [2 ~> 传图两式:URL vs Base64](#2 ~> 传图两式:URL vs Base64)
    • [3 ~> 多图同传:对比类任务的打开方式](#3 ~> 多图同传:对比类任务的打开方式)
    • [4 ~> 结构化输出:让视觉结果进流水线](#4 ~> 结构化输出:让视觉结果进流水线)
      • [4.1 Schema 设计的两个经验](#4.1 Schema 设计的两个经验)
      • [4.2 别忘了校验](#4.2 别忘了校验)
    • [5 ~> 生产三件套:重试、缓存、批量](#5 ~> 生产三件套:重试、缓存、批量)
    • [思考 && 总结](#思考 && 总结)
    • 结尾

【116.Python+AI】用Python调用GPT-4V分析图片:一句话让它描述、分类、检测你的图像

📖 文章简介: 本文系统讲解用Python调用多模态模型分析图片的完整流程,是多模态开发的入门实战。文章从最小的"看图说话"Demo切入------一张本地图片、一句提问、30行代码拿到描述;随后逐层补齐生产用法:图片传入的两种方式(公网URL直传与Base64编码内嵌,体积限制与各自的适用场景)、多图同传(一次请求分析多张图片,对比类任务的实现姿势)、指定分析维度(用Prompt引导模型按"主体/场景/文字/异常"的框架输出,避免自由发挥)、JSON结构化输出(配合response_format让视觉结果直接进入程序流水线,字段定义+校验的完整代码)、以及三个生产级细节(图片预处理降成本、错误重试、结果缓存)。每个能力点附可直接运行的Python代码,配以Mermaid流程图展示从图片到结构化数据的完整链路。读完你将能把"图片分析"像调用普通函数一样嵌入任何Python项目,适合刚接触多模态API的开发者阅读参考。


🎬 个人主页: 源码骑士

专栏传送门: 《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:

5年Android Framework系统开发经验,曾主导多项系统级性能优化专项

技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)

累计产出原创技术文章100+篇,文章以流程图为特色,被读者评价为"看一篇胜过啃一周源码"


导入语

上一篇建立了多模态的能力认知,这篇直接动手。目标很简单:让你的Python程序"看见"图片------扔给它一张照片,返回一段描述、一个分类、或者一份结构化的分析报告。

这个能力一旦入库,很多原本需要训练专用模型的需求瞬间平民化:商品图自动打标、用户上传内容初审、工单截图自动分类------以前要攒数据、训模型、搭服务,现在只是一次API调用。

我们从30行的Hello World开始,逐步补齐URL/Base64两种传图方式、多图对比、结构化输出这些生产必备技能。全程基于OpenAI兼容接口------同一套代码,换base_url就能切到Qwen-VL等国产模型(第40篇的统一调用思路在这里继续生效)。


1 ~> Hello World:30行看图说话

python 复制代码
import base64
from openai import OpenAI

client = OpenAI()   # 读环境变量 OPENAI_API_KEY

def encode_image(path: str) -> str:
    """本地图片 → Base64字符串"""
    with open(path, "rb") as f:
        return base64.b64encode(f.read()).decode()

def describe_image(path: str, question: str = "请详细描述这张图片") -> str:
    b64 = encode_image(path)
    resp = client.chat.completions.create(
        model="gpt-4o-mini",           # 带视觉能力的模型
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": question},
                {"type": "image_url",
                 "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
            ],
        }],
    )
    return resp.choices[0].message.content

print(describe_image("product.jpg"))

注意请求体的结构:content不再是字符串,而是一个数组------文本块和图片块按顺序拼在一起。这个"多块拼装"的结构就是多模态请求的全部秘密。

1.1 调用链路

#mermaid-svg-mnYNKdwndPHdr7mp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mnYNKdwndPHdr7mp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mnYNKdwndPHdr7mp .error-icon{fill:#552222;}#mermaid-svg-mnYNKdwndPHdr7mp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mnYNKdwndPHdr7mp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mnYNKdwndPHdr7mp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mnYNKdwndPHdr7mp .marker.cross{stroke:#333333;}#mermaid-svg-mnYNKdwndPHdr7mp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mnYNKdwndPHdr7mp p{margin:0;}#mermaid-svg-mnYNKdwndPHdr7mp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mnYNKdwndPHdr7mp .cluster-label text{fill:#333;}#mermaid-svg-mnYNKdwndPHdr7mp .cluster-label span{color:#333;}#mermaid-svg-mnYNKdwndPHdr7mp .cluster-label span p{background-color:transparent;}#mermaid-svg-mnYNKdwndPHdr7mp .label text,#mermaid-svg-mnYNKdwndPHdr7mp span{fill:#333;color:#333;}#mermaid-svg-mnYNKdwndPHdr7mp .node rect,#mermaid-svg-mnYNKdwndPHdr7mp .node circle,#mermaid-svg-mnYNKdwndPHdr7mp .node ellipse,#mermaid-svg-mnYNKdwndPHdr7mp .node polygon,#mermaid-svg-mnYNKdwndPHdr7mp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mnYNKdwndPHdr7mp .rough-node .label text,#mermaid-svg-mnYNKdwndPHdr7mp .node .label text,#mermaid-svg-mnYNKdwndPHdr7mp .image-shape .label,#mermaid-svg-mnYNKdwndPHdr7mp .icon-shape .label{text-anchor:middle;}#mermaid-svg-mnYNKdwndPHdr7mp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mnYNKdwndPHdr7mp .rough-node .label,#mermaid-svg-mnYNKdwndPHdr7mp .node .label,#mermaid-svg-mnYNKdwndPHdr7mp .image-shape .label,#mermaid-svg-mnYNKdwndPHdr7mp .icon-shape .label{text-align:center;}#mermaid-svg-mnYNKdwndPHdr7mp .node.clickable{cursor:pointer;}#mermaid-svg-mnYNKdwndPHdr7mp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mnYNKdwndPHdr7mp .arrowheadPath{fill:#333333;}#mermaid-svg-mnYNKdwndPHdr7mp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mnYNKdwndPHdr7mp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mnYNKdwndPHdr7mp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mnYNKdwndPHdr7mp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mnYNKdwndPHdr7mp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mnYNKdwndPHdr7mp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mnYNKdwndPHdr7mp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mnYNKdwndPHdr7mp .cluster text{fill:#333;}#mermaid-svg-mnYNKdwndPHdr7mp .cluster span{color:#333;}#mermaid-svg-mnYNKdwndPHdr7mp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mnYNKdwndPHdr7mp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mnYNKdwndPHdr7mp rect.text{fill:none;stroke-width:0;}#mermaid-svg-mnYNKdwndPHdr7mp .icon-shape,#mermaid-svg-mnYNKdwndPHdr7mp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mnYNKdwndPHdr7mp .icon-shape p,#mermaid-svg-mnYNKdwndPHdr7mp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mnYNKdwndPHdr7mp .icon-shape .label rect,#mermaid-svg-mnYNKdwndPHdr7mp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mnYNKdwndPHdr7mp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mnYNKdwndPHdr7mp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mnYNKdwndPHdr7mp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 自由描述
JSON Mode
本地图片
Base64编码

或公网URL
组装content数组

文本块+图片块
多模态模型

GPT-4o/Qwen-VL
输出形式
文本回答
结构化字段

进程序流水线


2 ~> 传图两式:URL vs Base64

方式 写法 优点 缺点 适用
公网URL {"url": "https://..."} 请求体小 图片须公网可访问 图床/OSS上的图
Base64内嵌 data:image/jpeg;base64,... 图片不出门也能传 请求体膨胀约33% 本地/内网图片

Base64的两个实用细节:

bash 复制代码
细节一:体积限制
  单图建议压到 2MB 以内再编码
  原图10MB直接编码 → 请求体13MB+,又慢又可能超限

细节二:先缩放再编码,省钱大招
  视觉模型按图片token计费,分辨率越高越贵
  2048px的长边足够大多数分析场景
  → PIL缩一下再传,费用立降50%+
python 复制代码
from PIL import Image
import io

def encode_image_smart(path: str, max_side: int = 2048) -> str:
    """缩放后再编码:成本直降,分析质量几乎无损"""
    img = Image.open(path)
    img.thumbnail((max_side, max_side))
    buf = io.BytesIO()
    img.convert("RGB").save(buf, format="JPEG", quality=85)
    return base64.b64encode(buf.getvalue()).decode()

这一条是本文最立竿见影的成本优化:视觉调用的费用和图片分辨率直接挂钩,而2048px对"看图说话、分类、字段提取"这些任务绰绰有余。原图直传是新手最常见的浪费。


3 ~> 多图同传:对比类任务的打开方式

content数组里可以放多个图片块------对比、找不同、序列理解类任务靠它:

python 复制代码
def compare_images(paths: list[str], question: str):
    content = [{"type": "text", "text": question}]
    for p in paths:
        content.append({
            "type": "image_url",
            "image_url": {"url": f"data:image/jpeg;base64,{encode_image_smart(p)}"},
        })
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": content}],
    )
    return resp.choices[0].message.content

# 用法示例
compare_images(
    ["design_v1.png", "design_v2.png"],
    "对比两版设计稿,列出所有视觉差异,按区域逐条说明",
)

典型场景:设计稿版本对比、设备前后照片巡检、多帧截图还原操作序列。注意图片数量与费用成正比------多图任务记得先用上一节的缩放控制成本。


4 ~> 结构化输出:让视觉结果进流水线

自由文本对人是友好的,对程序是灾难。生产场景必须JSON化(第46篇JSON Mode的组合技):

python 复制代码
import json

PRODUCT_SCHEMA = {
    "type": "object",
    "properties": {
        "category": {"type": "string",
                     "description": "商品类目:服装/数码/食品/家居/其他"},
        "brand_visible": {"type": "boolean", "description": "是否可见品牌logo"},
        "text_in_image": {"type": "array", "items": {"type": "string"},
                          "description": "图中出现的文字"},
        "quality_issues": {"type": "array", "items": {"type": "string"},
                           "description": "画质问题:模糊/过曝/水印/截断"},
    },
    "required": ["category", "brand_visible", "text_in_image", "quality_issues"],
}

def analyze_product(path: str) -> dict:
    b64 = encode_image_smart(path)
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text",
                 "text": "分析这张商品图,按schema输出结构化结果"},
                {"type": "image_url",
                 "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
            ],
        }],
        response_format={
            "type": "json_schema",
            "json_schema": {"name": "product_analysis", "schema": PRODUCT_SCHEMA},
        },
    )
    return json.loads(resp.choices[0].message.content)

4.1 Schema 设计的两个经验

bash 复制代码
经验一:枚举值写死在description里
  "服装/数码/食品/家居/其他" ------ 给了封闭选项,
  模型就不会发明"美妆个护"这种你系统里没有的类目

经验二:每个字段都是一次"显式提问"
  没有quality_issues字段,模型看到模糊图也不会主动提
  → Schema即提问清单:你想要什么信息,就定义什么字段

4.2 别忘了校验

第115篇强调过"输出当初稿用"------结构化之后加一层业务校验,流水线才算闭环:

python 复制代码
VALID_CATEGORIES = {"服装", "数码", "食品", "家居", "其他"}

def analyze_product_safe(path: str) -> dict:
    result = analyze_product(path)
    if result["category"] not in VALID_CATEGORIES:
        result["category"] = "其他"           # 兜底,防脏数据入库
        result["_need_review"] = True          # 打标转人工
    return result

5 ~> 生产三件套:重试、缓存、批量

python 复制代码
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=2, max=10))
def analyze_with_retry(path):
    return analyze_product_safe(path)

# 结果缓存:同一张图不分析两次(第101篇的缓存思想)
import hashlib

def analyze_cached(path):
    key = hashlib.md5(open(path, "rb").read()).hexdigest()
    if cached := redis_get(f"vision:{key}"):
        return json.loads(cached)
    result = analyze_with_retry(path)
    redis_set(f"vision:{key}", json.dumps(result), ex=86400)
    return result

批量任务(比如给一万张商品图打标)请走第44篇的asyncio并发方案------视觉API一样有RPM限制,指数退避重试是必修课。


思考 && 总结

  1. 多模态请求的秘密是content数组: 文本块+图片块按序拼装,换base_url即可切换到国产视觉模型。
  2. 先缩放再Base64: 长边2048px对分析任务绰绰有余,费用立降50%+------原图直传是新手最大的浪费。
  3. 多图同传解锁对比任务: 版本对比、巡检找茬、序列理解;图片数量与费用成正比,缩放先行。
  4. Schema即提问清单: 想要什么信息就定义什么字段;枚举值写死在description里防模型自由发挥;出口处加业务校验。
  5. 生产三件套别省略: 重试抗抖动、缓存防重复、批量走异步------视觉API的RPM限制和文本API一样严格。

会分析单张图片了,下一篇上一个更值钱的场景:文档智能解析------让AI看懂你的PDF合同、发票、报告,把关键字段抠出来直接入库。财务和法务部门的自动化,从这篇开始。


结尾

各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!

源码骑士 --- Android Framework & 全栈开发

👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长

❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量

收藏:把核心知识点存好,在需要时随时查、随时用

💬 评论:分享你的经验或疑问,评论区一起交流避坑

🔄 一键四连:不要忘记给博主"一键四连"哦!

🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向

结语:一个content数组、一个JSON Schema------图片从"程序看不懂的二进制"变成"流水线里的结构化数据",靠的就是这两个不起眼的结构。你的第一个视觉应用,今晚就能跑起来。不要忘记给博主"一键四连"哦!

相关推荐
老郑聊AI业财智造1 小时前
TensorFlow 技术架构与源码分析
人工智能·python·深度学习·架构·tensorflow·软件工程
民乐团扒谱机1 小时前
【微科普】压缩感知(CS):违反了奈奎斯特采样定理?不先采集也能还原信号?大白话讲透稀疏采样、L1重建与OMP,一文吃透附代码
python·神经网络·线性代数·算法·数学建模·压缩感知·奈奎斯特
88号技师1 小时前
2026年SCI-分数牛顿衍生优化算法Fractional Newton-derived optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
承渊政道1 小时前
【Python编程—从入门到实践】(Python操作列表深度教程:循环、切片、元组与代码风格)
开发语言·python·pycharm·列表·元组·切片
guwentian1 小时前
一文搞懂 AI Agent 安全护栏:用 Python 手写工具网关,把越权挡在执行层
人工智能·python·安全
布值倒区什么name1 小时前
Pycharm2026找不到编译器 点击运行运行不了
开发语言·python·pycharm
Python私教1 小时前
软件开发报价为什么能差 3 倍:需求范围、验收标准和变更成本怎么算
后端·python·架构
水獭比特1 小时前
Pydantic AI 2.33.0 遇上 Anthropic SDK 1.0:别让 httpx2 在运行时才暴雷
人工智能·python
智购科技自动售货机工厂1 小时前
2026自动售货机电机驱动芯片选型:从L298N到DRV8870的工程实践~YH
大数据·开发语言·数据库·人工智能·单片机·嵌入式硬件·scikit-learn