识别的结果还是挺准确的,而且支持本地或者网络图片的内容识别,官方文档也有说明:www.volcengine.com/docs/82379/...,使用的视觉理解这个接口,
部分大模型具备视觉理解能力,如当您传入图片时,大模型可以理解图片里的视觉信息,并结合这些信息完成如描述图片等图片相关任务。通过这篇教程,您可以学习到如何通过调用大模型 API 来识别传入图片里的信息。
官方使用示例:
ini
import os
# 通过 pip install volcengine-python-sdk[ark] 安装方舟SDK
from volcenginesdkarkruntime import Ark
# 替换 <Model> 为模型的Model ID
model="<Model>"
# 初始化Ark客户端,从环境变量中读取您的API Key
client = Ark(
api_key=os.getenv('ARK_API_KEY'),
)
# 创建一个对话请求
response = client.chat.completions.create(
# 指定您部署了视觉理解大模型的推理接入点ID
model = model,
messages = [
{
# 指定消息的角色为用户
"role": "user",
"content": [
# 文本消息,希望模型根据图片信息回答的问题
{"type": "text", "text": "支持输入是图片的模型系列是哪个?"},
# 图片信息,希望模型理解的图片
{"type": "image_url", "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/ark_demo_img_1.png"}
},
],
}
],
)
print(response.choices[0].message.content)
本地或网络图像识别
部分大模型具备视觉理解能力,如当您传入图片时,大模型可以理解图片里的视觉信息,并结合这些信息完成如描述图片等图片相关任务。通过这篇教程,您可以学习到如何通过调用大模型 API 来识别传入图片里的信息。
示例代码:
python
import base64
from openai import OpenAI
# 请确保您已将 API Key 存储在环境变量 ARK_API_KEY 中
# 初始化Ark客户端,从环境变量中读取您的API Key
client = OpenAI(
# 此为默认路径,您可根据业务所在地域进行配置
base_url="https://ark.cn-beijing.volces.com/api/v3",
# 从环境变量中获取您的 API Key。此为默认方式,您可根据需要进行修改
api_key='key',
)
# 定义方法将指定路径图片转为Base64编码
def encode_image(img_path):
with open(img_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 需要传给大模型的图片
image_path = "down2.png"
# 将图片转为Base64编码
base64_image = encode_image(image_path)
response = client.chat.completions.create(
# 指定您创建的方舟推理接入点 ID,此处已帮您修改为您的推理接入点 ID
model="doubao-1-5-vision-pro-32k-250115",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "请返回这种图片中橙色的单词给我"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{base64_image}"
},
},
],
}
],
)
print(response.choices[0])
print("content: ", response.choices[0].message.content)
多图像输入
API 可以支持接受和处理多个图像输入,这些图像可以通过图片可访问 URL 或图片转为 Base64 编码后输入,模型将结合所有传入的图像中的信息来回答问题。
python
import os
# 通过 pip install volcengine-python-sdk[ark] 安装方舟SDK
from volcenginesdkarkruntime import Ark
# 从环境变量中获取API Key
client = Ark(
api_key=os.getenv('ARK_API_KEY'),
)
response = client.chat.completions.create(
# 替换 <Model> 为模型的Model ID
model="<Model>",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "支持输入是图片的模型系列是哪个?同时,豆包应用场景有哪些?"},
{"type": "image_url","image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/ark_demo_img_1.png"}},
{"type": "image_url","image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/ark_demo_img_2.png"}},
],
}
],
)
print(response.choices[0])
Base64 编码输入
如果你要传入的图片在本地,你可以将这个图片转化为 Base64 编码,然后提交给大模型。下面是一个简单的示例代码。
python
import base64
import os
# 通过 pip install volcengine-python-sdk[ark] 安装方舟SDK
from volcenginesdkarkruntime import Ark
# 初始化一个Client对象,从环境变量中获取API Key
client = Ark(
api_key=os.getenv('ARK_API_KEY'),
)
# 定义方法将指定路径图片转为Base64编码
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 需要传给大模型的图片
image_path = "path_to_your_image.jpg"
# 将图片转为Base64编码
base64_image = encode_image(image_path)
response = client.chat.completions.create(
# 替换 <Model> 为模型的Model ID
model="<Model>",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "图片里讲了什么?",
},
{
"type": "image_url",
"image_url": {
# 需要注意:传入Base64编码前需要增加前缀 data:image/{图片格式};base64,{Base64编码}:
# PNG图片:"url": f"data:image/png;base64,{base64_image}"
# JEPG图片:"url": f"data:image/jpeg;base64,{base64_image}"
# WEBP图片:"url": f"data:image/webp;base64,{base64_image}"
"url": f"data:image/<IMAGE_FORMAT>;base64,{base64_image}"
},
},
],
}
],
)
print(response.choices[0])
图文混排
支持灵活地传入提示词和图片信息的方式,您可以任意调整传图图片和文本的顺序,以及在Systerm message
或者User message
传入图文信息。模型会根据顺序返回处理信息的结果,示例如下。
python
import os
# 安装方舟SDK, 可参考文档安装 https://www.volcengine.com/docs/82379/1399008#%E6%96%B9%E8%88%9F-python-sdk
from volcenginesdkarkruntime import Ark
# 初始化Ark客户端,从环境变量中读取您的API Key
client = Ark(
api_key=os.getenv('ARK_API_KEY'),
)
# 创建一个对话请求
response = client.chat.completions.create(
# 替换 <Model> 为模型的Model ID
model="<Model>",
messages=[
{
"role": "system",
"content": [
{"type": "text", "text": "下面人物是目标人物"},
{
"type": "image_url",
"image_url": {
"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/target.png"
},
},
{"type": "text", "text": "请确认下面图片中是否含有目标人物"},
],
},
{
"role": "user",
"content": [
{"type": "text", "text": "图片1中是否含有目标人物"},
{
"type": "image_url",
"image_url": {
"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/scene_01.png"
},
},
{"type": "text", "text": "图片2中是否含有目标人物"},
{
"type": "image_url",
"image_url": {
"url": "https://ark-project.tos-cn-beijing.volces.com/doc_image/scene_02.png"
},
},
],
},
],
)
print(response.choices[0].message.content)
最佳实践
下面介绍具体场景的应用案例,包括数据处理以及模型调用等端到端的案例。
上传本地图片进行分析
处理图片通常会与网络存储结合起来使用,下面介绍如何结合对象存储 TOS,来实现完整的图片处理流程。
代码流程
完整流程会分成以下步骤:
- 压缩图片。分辨率会影响 token 消耗数量,可以通过图片压缩,来节省网络、存储以及模型分析成本。
- 将压缩后的图片上传至对象存储 TOS,并为图片生成预签名 URL。
- 调用视觉理解大模型分析图片。
python
import os
import tos
from PIL import Image
from tos import HttpMethodType
from volcenginesdkarkruntime import Ark
# 从环境变量获取 AK/SK/APIKEY信息
ak = os.getenv('VOLC_ACCESSKEY')
sk = os.getenv('VOLC_SECRETKEY')
api_key = os.getenv('ARK_API_KEY')
# 配置视觉理解模型的 Model ID
model_id = '<Model>'
# 压缩前图片
original_file = "original_image.jpeg"
# 压缩后图片存放路径
compressed_file = "comressed_image.jpeg"
# 压缩的目标图片大小,300KB
target_size = 300 * 1024
# endpoint 和 region 填写Bucket 所在区域对应的Endpoint。
# 以华北2(北京)为例,region 填写 cn-beijing。
# 公网域名endpoint 填写 tos-cn-beijing.volces.com
endpoint, region = "tos-cn-beijing.volces.com", "cn-beijing"
# 对象桶名称
bucket_name = "demo-bucket-test"
# 对象名称,例如 images 下的 compressed_image.jpeg 文件,则填写为 images/compressed_image.jpeg
object_key = "images/compressed_image.jpeg"
def compress_image(input_path, output_path):
img = Image.open(input_path)
current_size = os.path.getsize(input_path)
# 粗略的估计压缩质量,也可以从常量开始,逐步减小压缩质量,直到文件大小小于目标大小
image_quality = int(float(target_size / current_size) * 100)
img.save(output_path, optimize=True, quality=int(float(target_size / current_size) * 100))
# 如果压缩后文件大小仍然大于目标大小,则继续压缩
# 压缩质量递减,直到文件大小小于目标大小
while os.path.getsize(output_path) > target_size:
img = Image.open(output_path)
image_quality -= 10
if image_quality <= 0:
break
img.save(output_path, optimize=True, quality=image_quality)
return image_quality
def upload_tos(filename, tos_endpoint, tos_region, tos_bucket_name, tos_object_key):
# 创建 TosClientV2 对象,对桶和对象的操作都通过 TosClientV2 实现
tos_client, inner_tos_client = tos.TosClientV2(ak, sk, tos_endpoint, tos_region), tos.TosClientV2(ak, sk,
tos_endpoint,
tos_region)
try:
# 将本地文件上传到目标桶中, filename为本地压缩后图片的完整路径
tos_client.put_object_from_file(tos_bucket_name, tos_object_key, filename)
# 获取上传后预签名的 url
return inner_tos_client.pre_signed_url(HttpMethodType.Http_Method_Get, tos_bucket_name, tos_object_key)
except Exception as e:
if isinstance(e, tos.exceptions.TosClientError):
# 操作失败,捕获客户端异常,一般情况为非法请求参数或网络异常
print('fail with client error, message:{}, cause: {}'.format(e.message, e.cause))
elif isinstance(e, tos.exceptions.TosServerError):
# 操作失败,捕获服务端异常,可从返回信息中获取详细错误信息
print('fail with server error, code: {}'.format(e.code))
# request id 可定位具体问题,强烈建议日志中保存
print('error with request id: {}'.format(e.request_id))
print('error with message: {}'.format(e.message))
print('error with http code: {}'.format(e.status_code))
else:
print('fail with unknown error: {}'.format(e))
raise e
if __name__ == "__main__":
print("----- 压缩图片 -----")
quality = compress_image(original_file, compressed_file)
print("Compressed Image Quality: {}".format(quality))
print("----- 上传至TOS -----")
pre_signed_url_output = upload_tos(compressed_file, endpoint, region, bucket_name, object_key)
print("Pre-signed TOS URL: {}".format(pre_signed_url_output.signed_url))
print("----- 传入图片调用视觉理解模型 -----")
client = Ark(api_key=api_key)
# 图片输入:
response = client.chat.completions.create(
# 配置推理接入点
model=model_id,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Which is the most secure payment app according to Americans?"},
{
"type": "image_url",
"image_url": {
"url": pre_signed_url_output.signed_url
}
},
],
}
],
)
print(response.choices[0])
对视频内容进行内容理解
当您需要对视频内容进行理解时,可以对视频进行抽帧,生成若干图片后,再将图片结合文本作为提示词内容,请求视频理解的模型服务。下面是使用 Python 脚本实现的方案,供您参考。
代码流程
- 视频文件读取:从本地路径读取视频文件。
- 对视频进行抽帧,可以支持 2 种策略:
CONSTANT_INTERVAL
:固定时间间隔抽帧,如 1s 一帧,时间间隔可以调整。EVEN_INTERVAL
:固定总帧数均匀抽帧,如不管视频时长,总共抽 20 帧,抽取的总帧数可以调整。
- 压缩帧图像:为了降低网络传输时延,以及减少 token 用量,将图像等比例压缩至 640*480 以内。
- 请求视觉理解模型:组装请求体,包含提示词(Prompt)及所有帧图像的 Base64,并使用方舟
ChatCompletion
接口调用模型服务。
完整的 Python 脚本
python
from typing import Optional
# 导入OpenCV库,用于视频处理
import cv2
# 导入时间库,用于计时
import time
# 导入文件操作库,用于删除目录
import shutil
# 导入枚举库,用于定义抽帧策略
from enum import Enum
# 导入Base64编码库,用于将图片转换为Base64编码
import base64
# 导入操作系统库,用于文件路径操作
import os
# 通过 pip install volcengine-python-sdk[ark] 安装方舟SDK
from volcenginesdkarkruntime._exceptions import ArkAPIError
# 导入方舟SDK,用于调用方舟API
from volcenginesdkarkruntime import Ark
# 定义抽帧策略枚举类
class Strategy(Enum):
# 固定间隔抽帧策略,例如每1秒抽一帧
CONSTANT_INTERVAL = "constant_interval"
# 均匀间隔抽帧策略,根据设定的最大帧数均匀从视频全长度抽取
EVEN_INTERVAL = "even_interval"
def preprocess_video(
video_file_path: str,
output_dir: str,
extraction_strategy: Optional[Strategy] = Strategy.EVEN_INTERVAL,
interval_in_seconds: Optional[float] = 1,
max_frames: Optional[int] = 10,
keyframe_naming_template: str = "frame_{:04d}.jpg",
) -> list[str]:
"""将视频按照指定策略抽帧
参数:
video_file_path (str): 视频文件路径
output_dir (str): 输出目录
extraction_strategy (Optional[Strategy], optional): 抽帧策略。
固定间隔 比如 1s 抽一帧 或
均匀间隔 根据设定的最大帧数 均匀从视频全长度均匀抽取
默认固定间隔 1s 抽一帧
interval_in_seconds (Optional[float], optional): 固定间隔抽帧的间隔时间. 默认 1s 抽一帧
max_frames (Optional[int], optional): 最大抽帧帧数. 默认 10 帧
keyframe_naming_template (_type_, optional): 抽帧图片命名模板
返回:
list[str]: 抽帧图片路径列表
"""
# 检查输出目录是否存在,如果不存在则创建
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 使用OpenCV打开视频文件
cap = cv2.VideoCapture(video_file_path)
# 获取视频的帧率
fps = cap.get(cv2.CAP_PROP_FPS)
# 获取视频的总帧数
length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 根据策略选择抽帧间隔
if extraction_strategy == Strategy.CONSTANT_INTERVAL:
# 计算固定间隔抽帧的帧间隔
frame_interval = int(fps * interval_in_seconds)
elif extraction_strategy == Strategy.EVEN_INTERVAL:
# 计算均匀间隔抽帧的帧间隔
frame_interval = int(length / max_frames)
else:
# 如果策略无效,抛出异常
raise ValueError("Invalid extraction strategy")
# 初始化帧计数器
frame_count = 0
# 初始化关键帧列表
keyframes = []
# 循环读取视频帧
while True:
# 读取一帧
ret, frame = cap.read()
# 如果读取失败,跳出循环
if not ret:
break
# 如果当前帧是关键帧
if frame_count % frame_interval == 0:
# 生成关键帧的文件名
image_path = os.path.join(
output_dir, keyframe_naming_template.format(len(keyframes))
)
# 将关键帧保存为图片
cv2.imwrite(
image_path,
frame,
)
# 将关键帧路径添加到列表中
keyframes.append(image_path)
# 增加帧计数器
frame_count += 1
# 如果关键帧数量达到最大值,跳出循环
if len(keyframes) >= max_frames:
break
print("【视频抽帧完成】")
print("【抽取帧数】", len(keyframes))
# 返回关键帧路径列表
return keyframes
def resize(image):
"""
调整图片大小以适应指定的尺寸。
参数:
image (numpy.ndarray): 输入的图片,格式为numpy数组。
返回:
numpy.ndarray: 调整大小后的图片。
"""
# 获取图片的原始高度和宽度
height, width = image.shape[:2]
# 根据图片的宽高比确定目标尺寸
if height < width:
target_height, target_width = 480, 640
else:
target_height, target_width = 640, 480
# 如果图片尺寸已经小于或等于目标尺寸,则直接返回原图片
if height <= target_height and width <= target_width:
return image
# 计算新的高度和宽度,保持图片的宽高比
if height / target_height < width / target_width:
new_width = target_width
new_height = int(height * (new_width / width))
else:
new_height = target_height
new_width = int(width * (new_height / height))
# 调整图片大小
return cv2.resize(image, (new_width, new_height))
# 定义方法将指定路径图片resize到合适大小并转为Base64编码
def encode_image(image_path: str) -> str:
"""
将指定路径的图片进行编码
参数:
image_path (str): 图片文件的路径
返回:
str: 编码后的图片字符串
"""
# 读取图片
image = cv2.imread(image_path)
# 调整图片大小
image_resized = resize(image)
# 将图片编码为JPEG格式
_, encoded_image = cv2.imencode(".jpg", image_resized)
# 将编码后的图片转换为Base64字符串
return base64.b64encode(encoded_image).decode("utf-8")
def construct_messages(image_paths: list[str], prompt: str) -> list[dict]:
"""
构造包含文本和图像的消息列表。
参数:
image_paths (list[str]): 图像文件路径列表。
prompt (str): 文本提示。
返回:
list[dict]: 包含文本和图像的消息列表。
"""
print("【组装请求参数】")
# 初始化消息内容列表,包含一个文本消息
content = [
{
"type": "text",
"text": prompt,
},
]
# 遍历图像路径列表
for image_path in image_paths:
# 为每个图像路径构造一个图像URL消息
content.append(
{
"type": "image_url",
"image_url": {
# 使用Base64编码将图像转换为数据URL
"url": f"data:image/jpeg;base64,{encode_image(image_path)}",
# 指定图像细节级别为低
"detail":"low"
},
}
)
# 返回包含文本和图像的消息列表
return [
{
"role": "user",
"content": content,
}
]
if __name__ == "__main__":
# 替换为您的视频文件的路径
video_path = "<VIDEO_FILE_PATH>"
# 环境变量中读取的API Key,请提前配置好您的API Key
ark_api_key = os.environ.get("ARK_API_KEY")
# 替换 <Model> 为模型的Model ID
model_id = "<Model>"
# 替换为您的文本提示,这里仅做示例使用
prompt = "描述视频内容"
since = time.time()
# 删除历史帧
if os.path.exists("videoFrames"):
shutil.rmtree("videoFrames")
selected_images = preprocess_video(
video_file_path=video_path,
output_dir="videoFrames",
# 两种抽帧策略
# 1. CONSTANT_INTERVAL:固定时间间隔抽帧,可以调整时间间隔
# 2. EVEN_INTERVAL:固定总帧数均匀抽帧,可以调整总帧数
extraction_strategy=Strategy.CONSTANT_INTERVAL,
# extraction_strategy=Strategy.EVEN_INTERVAL,
# 固定抽帧策略时对应的抽帧间隔
interval_in_seconds=1,
# 视频抽取的最大帧数
max_frames=20
)
print("【帧图像压缩完成】")
preprocess_time_cost = time.time() - since
client = Ark(api_key=ark_api_key)
since = time.time()
try:
response = client.chat.completions.create(
model=model_id,
messages=construct_messages(
selected_images,
prompt,
)
)
print("【视频理解结果】\n", response.choices[0].message.content)
print("\n【总tokens消耗】", response.usage.total_tokens)
print("- 输入tokens:", response.usage.prompt_tokens)
print("- 输出tokens:", response.usage.completion_tokens)
except ArkAPIError as e:
print(e)
api_time_cost = time.time() - since
print("\n【全链路整体耗时】", round(preprocess_time_cost + api_time_cost, 2))
print("- 视频预处理耗时:", round(preprocess_time_cost, 2))
print("- 结果生成耗时:", round(api_time_cost, 2))