欢迎光临
我们一直在努力

Ostrakon-VL-8B基础教程:Python调用API实现批量库存盘点自动化

Ostrakon-VL-8B基础教程:Python调用API实现批量库存盘点自动化

1. 引言

如果你是零售或餐饮行业的从业者,每天面对成百上千的商品库存,手动盘点是不是让你头疼不已?拍照、记录、核对、录入系统……一套流程下来,半天时间就没了,还容易出错。

今天我要分享一个能让你彻底告别手动盘点的自动化方案。我们利用Ostrakon-VL-8B这个专门为零售餐饮场景优化的多模态大模型,通过Python脚本批量处理货架照片,自动识别商品、统计数量,还能检查陈列合规性。整个过程完全自动化,效率提升至少10倍。

Ostrakon-VL-8B是基于Qwen3-VL-8B微调的开源模型,它在商品识别、货架分析、价格标签识别等方面表现特别出色。更重要的是,它提供了WebUI和API两种使用方式,我们可以通过API实现批量自动化处理。

这篇文章我会手把手教你如何用Python调用Ostrakon-VL-8B的API,搭建一个完整的库存盘点自动化系统。即使你只有基础的Python知识,跟着步骤走也能轻松搞定。

2. 环境准备与快速部署

2.1 系统要求检查

在开始之前,我们先确认一下你的环境是否符合要求:

  • GPU:需要NVIDIA RTX 4090D或同等性能的显卡,显存至少24GB
  • 内存:建议32GB以上
  • 存储:至少50GB可用空间
  • Python:3.10或更高版本
  • 操作系统:Ubuntu 20.04/22.04或CentOS 8+

如果你使用的是云服务器,确保选择带有足够GPU资源的实例。本地部署的话,检查一下显卡驱动是否安装正确。

2.2 一键部署Ostrakon-VL-8B

如果你还没有部署Ostrakon-VL-8B,这里提供最简单的部署方法。假设你已经有了满足要求的服务器环境:

# 1. 克隆项目代码
git clone https://github.com/Ostrakon-VL/Ostrakon-VL.git
cd Ostrakon-VL

# 2. 创建Python虚拟环境
python -m venv venv
source venv/bin/activate

# 3. 安装依赖包
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cu118
pip install -r requirements.txt

# 4. 下载模型权重
# 如果你有HuggingFace账号,可以直接下载
# 或者从镜像站下载预训练好的模型

# 5. 启动WebUI服务
python webui.py –share –port 7860

部署完成后,在浏览器中打开 http://你的服务器IP:7860 就能看到WebUI界面了。这个界面主要用于测试和手动操作,我们的自动化系统将通过API调用。

2.3 安装Python依赖包

我们需要安装几个关键的Python包来构建自动化系统:

pip install requests pillow opencv-python pandas numpy

简单解释一下这些包的作用:

  • requests:用于发送HTTP请求调用API
  • pillow:处理图片文件
  • opencv-python:图片处理和格式转换
  • pandas:处理和分析识别结果数据
  • numpy:数值计算

3. Python调用API基础

3.1 理解Ostrakon-VL-8B的API接口

Ostrakon-VL-8B提供了两种API调用方式:

  • 同步API:发送请求后等待返回结果,适合单张图片处理
  • 批量API:一次发送多张图片,适合大规模处理
  • 我们先从最简单的同步API开始。API的基本地址是 http://你的服务器IP:7860/api/v1/chat/completions,使用POST方法发送请求。

    3.2 第一个API调用示例

    让我们写一个最简单的Python脚本来测试API是否正常工作:

    import requests
    import base64
    import json

    def test_api_connection(server_url):
    """
    测试API连接是否正常
    """
    # 准备测试图片(这里用base64编码的简单图片)
    # 实际使用时,我们会从文件读取图片
    test_image_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

    # 构建请求数据
    payload = {
    "model": "Ostrakon-VL-8B",
    "messages": [
    {
    "role": "user",
    "content": [
    {
    "type": "text",
    "text": "请描述这张图片的内容"
    },
    {
    "type": "image_url",
    "image_url": {
    "url": f"data:image/jpeg;base64,{test_image_base64}"
    }
    }
    ]
    }
    ],
    "max_tokens": 500
    }

    # 发送请求
    try:
    response = requests.post(
    f"{server_url}/api/v1/chat/completions",
    json=payload,
    timeout=30
    )

    if response.status_code == 200:
    result = response.json()
    print("API连接成功!")
    print(f"模型回复:{result['choices'][0]['message']['content']}")
    return True
    else:
    print(f"API请求失败,状态码:{response.status_code}")
    print(f"错误信息:{response.text}")
    return False

    except Exception as e:
    print(f"连接API时发生错误:{str(e)}")
    return False

    # 使用示例
    if __name__ == "__main__":
    server_url = "http://localhost:7860" # 修改为你的服务器地址
    test_api_connection(server_url)

    运行这个脚本,如果看到"API连接成功!"的提示,说明一切正常。接下来我们就可以开始构建真正的库存盘点系统了。

    4. 构建库存盘点自动化系统

    4.1 系统架构设计

    我们的自动化系统包含以下几个核心模块:

    库存盘点自动化系统
    ├── 图片采集模块(从摄像头或手机获取货架照片)
    ├── 图片预处理模块(调整大小、格式转换)
    ├── API调用模块(发送图片给Ostrakon-VL-8B)
    ├── 结果解析模块(提取商品信息和数量)
    ├── 数据汇总模块(生成盘点报告)
    └── 异常检测模块(识别陈列问题)

    整个流程是这样的:

  • 员工用手机或摄像头拍摄货架照片
  • 系统自动收集并预处理这些照片
  • 调用Ostrakon-VL-8B API分析每张照片
  • 解析API返回的结果,提取商品信息
  • 汇总所有数据,生成盘点报告
  • 识别陈列不合规的地方并标记
  • 4.2 图片预处理函数

    在实际应用中,我们拍摄的照片可能大小不一、格式不同。为了让模型识别更准确,我们需要对图片进行预处理:

    from PIL import Image
    import cv2
    import os

    def preprocess_image(image_path, output_size=(1024, 1024)):
    """
    预处理图片:调整大小、转换格式、增强质量
    """
    try:
    # 读取图片
    if isinstance(image_path, str):
    img = Image.open(image_path)
    else:
    # 如果是numpy数组(来自摄像头)
    img = Image.fromarray(image_path)

    # 转换为RGB模式(确保颜色正确)
    if img.mode != 'RGB':
    img = img.convert('RGB')

    # 调整大小,保持宽高比
    img.thumbnail(output_size, Image.Resampling.LANCZOS)

    # 创建新的画布,将图片放在中间
    new_img = Image.new('RGB', output_size, (255, 255, 255))
    img_width, img_height = img.size
    left = (output_size[0] – img_width) // 2
    top = (output_size[1] – img_height) // 2
    new_img.paste(img, (left, top))

    # 保存预处理后的图片
    temp_path = "temp_preprocessed.jpg"
    new_img.save(temp_path, "JPEG", quality=95)

    return temp_path

    except Exception as e:
    print(f"图片预处理失败:{str(e)}")
    return None

    def image_to_base64(image_path):
    """
    将图片转换为base64编码
    """
    try:
    with open(image_path, "rb") as image_file:
    encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
    return encoded_string
    except Exception as e:
    print(f"图片转base64失败:{str(e)}")
    return None

    4.3 核心API调用函数

    这是整个系统的核心,负责与Ostrakon-VL-8B通信:

    import requests
    import time
    from typing import List, Dict, Optional

    class OstrakonVLClient:
    def __init__(self, server_url: str = "http://localhost:7860"):
    """
    初始化Ostrakon-VL客户端
    """
    self.server_url = server_url
    self.api_endpoint = f"{server_url}/api/v1/chat/completions"

    def analyze_single_image(self, image_path: str, question: str) -> Optional[Dict]:
    """
    分析单张图片
    """
    try:
    # 预处理图片
    processed_path = preprocess_image(image_path)
    if not processed_path:
    return None

    # 转换为base64
    image_base64 = image_to_base64(processed_path)
    if not image_base64:
    return None

    # 构建请求
    payload = {
    "model": "Ostrakon-VL-8B",
    "messages": [
    {
    "role": "user",
    "content": [
    {"type": "text", "text": question},
    {
    "type": "image_url",
    "image_url": {
    "url": f"data:image/jpeg;base64,{image_base64}"
    }
    }
    ]
    }
    ],
    "max_tokens": 1000,
    "temperature": 0.1 # 低温度确保结果稳定
    }

    # 发送请求
    start_time = time.time()
    response = requests.post(
    self.api_endpoint,
    json=payload,
    timeout=60 # 图片分析可能需要较长时间
    )
    elapsed_time = time.time() – start_time

    if response.status_code == 200:
    result = response.json()
    # 清理临时文件
    if os.path.exists(processed_path):
    os.remove(processed_path)

    return {
    "success": True,
    "response": result['choices'][0]['message']['content'],
    "processing_time": elapsed_time
    }
    else:
    print(f"API请求失败:{response.status_code}")
    print(f"错误信息:{response.text}")
    return {
    "success": False,
    "error": f"HTTP {response.status_code}: {response.text}"
    }

    except requests.exceptions.Timeout:
    return {
    "success": False,
    "error": "请求超时,请检查网络连接或服务器状态"
    }
    except Exception as e:
    return {
    "success": False,
    "error": str(e)
    }

    def analyze_batch_images(self, image_paths: List[str], questions: List[str]) -> List[Dict]:
    """
    批量分析多张图片
    注意:Ostrakon-VL-8B目前不支持真正的批量API,
    这里是通过循环调用单张图片API实现的
    """
    results = []

    for i, (image_path, question) in enumerate(zip(image_paths, questions)):
    print(f"正在处理第 {i+1}/{len(image_paths)} 张图片…")

    result = self.analyze_single_image(image_path, question)
    if result:
    results.append(result)
    else:
    results.append({
    "success": False,
    "error": "处理失败",
    "image_path": image_path
    })

    # 避免请求过于频繁
    time.sleep(1)

    return results

    4.4 库存盘点专用问题模板

    针对不同的盘点需求,我们准备了一系列优化过的问题模板。这些问题经过测试,能获得最准确的识别结果:

    class InventoryQuestions:
    """
    库存盘点专用问题模板
    """

    @staticmethod
    def get_product_identification_question():
    """商品识别问题"""
    return """请仔细识别图片中的所有商品,按以下格式回答:
    1. 商品名称:[商品具体名称]
    2. 品牌:[品牌名称,如果可见]
    3. 数量:[估计数量]
    4. 位置描述:[在货架上的位置]
    5. 包装规格:[如500ml、1kg等]

    请列出所有可见商品,每个商品单独一行。"""

    @staticmethod
    def get_shelf_analysis_question():
    """货架分析问题"""
    return """请分析这个货架的陈列情况:
    1. 货架类型:[如饮料架、零食架、冷藏柜等]
    2. 陈列层数:[共几层]
    3. 每层商品种类:[每层主要是什么商品]
    4. 陈列整齐度:[1-10分,10分为最整齐]
    5. 需要补货的商品:[哪些商品数量明显不足]
    6. 陈列问题:[如商品倒置、标签不清等]"""

    @staticmethod
    def get_price_check_question():
    """价格标签检查问题"""
    return """请检查图片中的价格标签:
    1. 可见的价格标签数量:[个]
    2. 标签清晰度:[清晰/模糊/不可读]
    3. 价格信息是否完整:[是/否]
    4. 促销信息:[如有,请描述]
    5. 问题标签:[哪些标签有问题,具体问题是什么]"""

    @staticmethod
    def get_compliance_check_question():
    """合规检查问题"""
    return """请检查以下合规项目:
    1. 过期商品:[如有,请描述]
    2. 破损包装:[如有,请描述]
    3. 卫生状况:[清洁/一般/脏乱]
    4. 安全通道:[是否畅通]
    5. 消防设施:[是否被遮挡]
    6. 其他违规项:[如有,请描述]"""

    @staticmethod
    def get_inventory_count_question(product_name=None):
    """库存数量统计问题"""
    if product_name:
    return f"""请统计图片中'{product_name}'商品的数量。
    注意:请仔细数清楚,包括部分被遮挡的商品。
    如果无法确定具体数量,请给出估计范围。"""
    else:
    return """请统计图片中所有商品的总数量。
    按商品种类分别统计,格式为:商品名称: 数量"""

    5. 完整库存盘点系统实现

    5.1 主程序:自动化盘点流程

    现在我们把所有模块组合起来,创建一个完整的库存盘点系统:

    import os
    import json
    import pandas as pd
    from datetime import datetime
    from pathlib import Path

    class InventoryAutomationSystem:
    """
    库存盘点自动化系统
    """

    def __init__(self, server_url: str, output_dir: str = "./inventory_reports"):
    self.client = OstrakonVLClient(server_url)
    self.questions = InventoryQuestions()
    self.output_dir = output_dir

    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    def process_store_section(self, section_name: str, image_folder: str):
    """
    处理一个店铺区域的库存盘点
    """
    print(f"\\n{'='*50}")
    print(f"开始处理区域:{section_name}")
    print(f"{'='*50}")

    # 获取所有图片文件
    image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.webp']
    image_files = []

    for ext in image_extensions:
    image_files.extend(Path(image_folder).glob(f"*{ext}"))
    image_files.extend(Path(image_folder).glob(f"*{ext.upper()}"))

    if not image_files:
    print(f"在 {image_folder} 中未找到图片文件")
    return None

    print(f"找到 {len(image_files)} 张图片")

    # 为每张图片准备问题
    questions = []
    for img_path in image_files:
    # 根据文件名判断问题类型
    img_name = img_path.name.lower()

    if 'price' in img_name or '标签' in img_name:
    questions.append(self.questions.get_price_check_question())
    elif 'shelf' in img_name or '货架' in img_name:
    questions.append(self.questions.get_shelf_analysis_question())
    elif 'compliance' in img_name or '合规' in img_name:
    questions.append(self.questions.get_compliance_check_question())
    else:
    questions.append(self.questions.get_product_identification_question())

    # 批量分析图片
    results = self.client.analyze_batch_images(
    [str(img) for img in image_files],
    questions
    )

    # 解析结果
    inventory_data = self._parse_results(results, image_files)

    # 生成报告
    report = self._generate_report(section_name, inventory_data)

    # 保存结果
    self._save_results(section_name, inventory_data, report)

    return report

    def _parse_results(self, results, image_files):
    """
    解析API返回的结果
    """
    inventory_data = []

    for i, (result, img_path) in enumerate(zip(results, image_files)):
    if result.get('success'):
    response_text = result['response']

    # 这里可以根据实际返回格式进行解析
    # 示例:简单的关键词提取
    item_data = {
    'image_file': str(img_path),
    'processing_time': result.get('processing_time', 0),
    'raw_response': response_text,
    'product_count': self._extract_product_count(response_text),
    'has_issues': self._check_for_issues(response_text),
    'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    }

    inventory_data.append(item_data)

    print(f"图片 {i+1} 分析完成,耗时 {result.get('processing_time', 0):.2f}秒")
    else:
    print(f"图片 {i+1} 分析失败:{result.get('error', '未知错误')}")

    return inventory_data

    def _extract_product_count(self, text: str) -> int:
    """
    从响应文本中提取商品数量
    这是一个简单的示例,实际应用中可能需要更复杂的解析逻辑
    """
    import re

    # 查找数字模式
    patterns = [
    r'数量[::]\\s*(\\d+)',
    r'共\\s*(\\d+)\\s*个',
    r'总计[::]\\s*(\\d+)',
    r'(\\d+)\\s*件商品'
    ]

    for pattern in patterns:
    match = re.search(pattern, text)
    if match:
    try:
    return int(match.group(1))
    except:
    continue

    # 如果没有找到明确的数量,尝试统计提到的商品种类
    lines = text.split('\\n')
    product_lines = [line for line in lines if '商品' in line or '产品' in line]
    return len(product_lines)

    def _check_for_issues(self, text: str) -> bool:
    """
    检查是否有问题或异常
    """
    issue_keywords = ['问题', '异常', '不足', '缺少', '破损', '过期', '脏乱', '遮挡']

    for keyword in issue_keywords:
    if keyword in text:
    return True
    return False

    def _generate_report(self, section_name: str, inventory_data: list) -> dict:
    """
    生成盘点报告
    """
    if not inventory_data:
    return {"error": "没有有效数据"}

    # 计算统计信息
    total_images = len(inventory_data)
    successful_images = len([d for d in inventory_data if d.get('success', True)])
    total_products = sum([d.get('product_count', 0) for d in inventory_data])
    images_with_issues = len([d for d in inventory_data if d.get('has_issues', False)])

    avg_processing_time = sum([d.get('processing_time', 0) for d in inventory_data]) / total_images

    report = {
    'section_name': section_name,
    'report_date': datetime.now().strftime('%Y-%m-%d'),
    'report_time': datetime.now().strftime('%H:%M:%S'),
    'total_images_processed': total_images,
    'successful_images': successful_images,
    'success_rate': f"{(successful_images/total_images*100):.1f}%" if total_images > 0 else "0%",
    'total_products_identified': total_products,
    'images_with_issues': images_with_issues,
    'average_processing_time': f"{avg_processing_time:.2f}秒",
    'detailed_data': inventory_data
    }

    return report

    def _save_results(self, section_name: str, inventory_data: list, report: dict):
    """
    保存结果到文件
    """
    # 生成文件名
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    base_filename = f"{section_name}_{timestamp}"

    # 保存原始数据为JSON
    json_path = os.path.join(self.output_dir, f"{base_filename}_raw.json")
    with open(json_path, 'w', encoding='utf-8') as f:
    json.dump({
    'inventory_data': inventory_data,
    'report': report
    }, f, ensure_ascii=False, indent=2)

    # 保存报告为CSV(便于Excel打开)
    csv_data = []
    for item in inventory_data:
    csv_data.append({
    '图片文件': item['image_file'],
    '处理时间(秒)': f"{item.get('processing_time', 0):.2f}",
    '商品数量': item.get('product_count', 0),
    '存在问题': '是' if item.get('has_issues', False) else '否',
    '分析时间': item.get('timestamp', '')
    })

    if csv_data:
    df = pd.DataFrame(csv_data)
    csv_path = os.path.join(self.output_dir, f"{base_filename}_report.csv")
    df.to_csv(csv_path, index=False, encoding='utf-8-sig')

    # 保存汇总报告
    summary_path = os.path.join(self.output_dir, f"{base_filename}_summary.txt")
    with open(summary_path, 'w', encoding='utf-8') as f:
    f.write(f"库存盘点报告 – {section_name}\\n")
    f.write(f"生成时间:{report['report_date']} {report['report_time']}\\n")
    f.write("="*50 + "\\n\\n")
    f.write(f"处理图片总数:{report['total_images_processed']}\\n")
    f.write(f"成功分析图片:{report['successful_images']}\\n")
    f.write(f"分析成功率:{report['success_rate']}\\n")
    f.write(f"识别商品总数:{report['total_products_identified']}\\n")
    f.write(f"存在问题图片:{report['images_with_issues']}\\n")
    f.write(f"平均处理时间:{report['average_processing_time']}\\n\\n")

    if report['images_with_issues'] > 0:
    f.write("存在问题详情:\\n")
    for item in inventory_data:
    if item.get('has_issues', False):
    f.write(f"- {item['image_file']}\\n")

    print(f"\\n报告已保存到:")
    print(f"原始数据:{json_path}")
    print(f"CSV报告:{csv_path}")
    print(f"汇总报告:{summary_path}")

    # 使用示例
    if __name__ == "__main__":
    # 初始化系统
    system = InventoryAutomationSystem(
    server_url="http://localhost:7860", # 修改为你的服务器地址
    output_dir="./inventory_reports"
    )

    # 处理一个店铺区域
    # 假设图片存放在 ./store_section1 目录下
    report = system.process_store_section(
    section_name="饮料区",
    image_folder="./store_section1"
    )

    if report:
    print("\\n盘点完成!")
    print(f"区域:{report['section_name']}")
    print(f"识别商品总数:{report['total_products_identified']}")
    print(f"分析成功率:{report['success_rate']}")

    5.2 批量处理多个区域

    如果你的店铺有多个区域需要盘点,可以创建一个批量处理脚本:

    def batch_process_store_sections(store_layout):
    """
    批量处理整个店铺的所有区域
    """
    system = InventoryAutomationSystem(
    server_url="http://localhost:7860",
    output_dir="./full_store_inventory"
    )

    all_reports = {}

    for section_name, image_folder in store_layout.items():
    print(f"\\n开始处理:{section_name}")

    report = system.process_store_section(section_name, image_folder)

    if report:
    all_reports[section_name] = report

    # 短暂暂停,避免服务器压力过大
    import time
    time.sleep(2)

    # 生成总报告
    generate_summary_report(all_reports)

    return all_reports

    def generate_summary_report(all_reports):
    """
    生成店铺总盘点报告
    """
    total_products = 0
    total_images = 0
    total_issues = 0

    summary_lines = []
    summary_lines.append("="*60)
    summary_lines.append("店铺库存盘点总报告")
    summary_lines.append(f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    summary_lines.append("="*60)
    summary_lines.append("")

    for section_name, report in all_reports.items():
    total_products += report.get('total_products_identified', 0)
    total_images += report.get('total_images_processed', 0)
    total_issues += report.get('images_with_issues', 0)

    summary_lines.append(f"【{section_name}】")
    summary_lines.append(f" 识别商品:{report.get('total_products_identified', 0)}")
    summary_lines.append(f" 处理图片:{report.get('successful_images', 0)}/{report.get('total_images_processed', 0)}")
    summary_lines.append(f" 成功率:{report.get('success_rate', '0%')}")
    summary_lines.append(f" 存在问题:{report.get('images_with_issues', 0)}张")
    summary_lines.append("")

    summary_lines.append("="*60)
    summary_lines.append("店铺总计")
    summary_lines.append("="*60)
    summary_lines.append(f"总识别商品数:{total_products}")
    summary_lines.append(f"总处理图片数:{total_images}")
    summary_lines.append(f"总问题图片数:{total_issues}")

    # 保存总报告
    summary_path = "./full_store_inventory/summary_report.txt"
    with open(summary_path, 'w', encoding='utf-8') as f:
    f.write('\\n'.join(summary_lines))

    print(f"\\n总报告已保存到:{summary_path}")

    # 打印到控制台
    print('\\n'.join(summary_lines))

    # 定义店铺区域布局
    store_layout = {
    "饮料区": "./images/beverage_section",
    "零食区": "./images/snack_section",
    "冷藏柜": "./images/refrigerator",
    "收银台": "./images/cashier_area"
    }

    # 执行批量处理
    batch_process_store_sections(store_layout)

    6. 实用技巧与优化建议

    6.1 提升识别准确率的技巧

    在实际使用中,你可能发现某些商品的识别准确率不够高。这里有几个实用技巧:

  • 拍摄技巧优化
  • def optimize_photo_quality(image_path):
    """
    优化照片质量以提高识别准确率
    """
    # 1. 确保光线充足,避免反光和阴影
    # 2. 从正面拍摄,避免角度倾斜
    # 3. 确保商品标签清晰可见
    # 4. 避免商品重叠遮挡
    # 5. 每张照片只包含一个货架区域

    tips = """
    拍摄建议:
    1. 距离货架1-2米,保持水平
    2. 确保所有商品标签朝外
    3. 避免玻璃反光(冷藏柜)
    4. 分区域拍摄,不要试图一张照片拍整个货架
    5. 对焦在商品上,而不是背景
    """
    return tips

  • 问题优化策略
  • def get_optimized_question(image_context):
    """
    根据图片内容优化问题
    """
    # 如果是饮料区,问更具体的问题
    if "beverage" in image_context or "饮料" in image_context:
    return """请识别图片中的饮料商品:
    1. 按品牌分类(可口可乐、百事、康师傅等)
    2. 统计每个品牌的数量
    3. 检查是否有过期商品
    4. 价格标签是否清晰可见"""

    # 如果是生鲜区,关注新鲜度
    elif "fresh" in image_context or "生鲜" in image_context:
    return """请检查生鲜商品:
    1. 商品名称和种类
    2. 新鲜度评估(新鲜/一般/不新鲜)
    3. 包装完整性
    4. 陈列是否符合卫生标准"""

    # 默认问题
    else:
    return InventoryQuestions.get_product_identification_question()

    6.2 性能优化建议

    当处理大量图片时,性能可能成为问题。这里有几个优化建议:

  • 并发处理优化
  • import concurrent.futures
    import threading

    class ConcurrentOstrakonClient(OstrakonVLClient):
    """
    支持并发处理的客户端
    """

    def __init__(self, server_url: str, max_workers: int = 3):
    super().__init__(server_url)
    self.max_workers = max_workers
    self._lock = threading.Lock()

    def analyze_images_concurrently(self, image_paths: List[str], questions: List[str]):
    """
    并发分析多张图片
    """
    results = []

    with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
    # 提交所有任务
    future_to_image = {
    executor.submit(self.analyze_single_image, img_path, question): (img_path, question)
    for img_path, question in zip(image_paths, questions)
    }

    # 收集结果
    for future in concurrent.futures.as_completed(future_to_image):
    img_path, question = future_to_image[future]
    try:
    result = future.result(timeout=120) # 2分钟超时
    results.append(result)
    print(f"完成:{img_path}")
    except Exception as e:
    print(f"处理失败 {img_path}: {str(e)}")
    results.append({
    "success": False,
    "error": str(e),
    "image_path": img_path
    })

    return results

  • 缓存优化
  • import hashlib
    import pickle
    from functools import lru_cache

    class CachedOstrakonClient(OstrakonVLClient):
    """
    带缓存功能的客户端,避免重复分析相同图片
    """

    def __init__(self, server_url: str, cache_dir: str = "./cache"):
    super().__init__(server_url)
    self.cache_dir = cache_dir
    os.makedirs(cache_dir, exist_ok=True)

    def analyze_single_image(self, image_path: str, question: str) -> Optional[Dict]:
    """
    带缓存的图片分析
    """
    # 生成缓存键
    cache_key = self._generate_cache_key(image_path, question)
    cache_file = os.path.join(self.cache_dir, f"{cache_key}.pkl")

    # 检查缓存
    if os.path.exists(cache_file):
    try:
    with open(cache_file, 'rb') as f:
    cached_result = pickle.load(f)
    print(f"使用缓存结果:{image_path}")
    return cached_result
    except:
    pass

    # 调用父类方法分析
    result = super().analyze_single_image(image_path, question)

    # 保存到缓存
    if result and result.get('success'):
    try:
    with open(cache_file, 'wb') as f:
    pickle.dump(result, f)
    except:
    pass

    return result

    def _generate_cache_key(self, image_path: str, question: str) -> str:
    """
    生成缓存键
    """
    # 使用文件MD5和问题摘要作为缓存键
    with open(image_path, 'rb') as f:
    file_hash = hashlib.md5(f.read()).hexdigest()

    question_hash = hashlib.md5(question.encode()).hexdigest()

    return f"{file_hash}_{question_hash}"

    6.3 错误处理与重试机制

    在实际生产环境中,网络波动或服务器问题可能导致请求失败。我们需要一个健壮的错误处理机制:

    class RobustOstrakonClient(OstrakonVLClient):
    """
    带重试机制的客户端
    """

    def __init__(self, server_url: str, max_retries: int = 3, retry_delay: int = 5):
    super().__init__(server_url)
    self.max_retries = max_retries
    self.retry_delay = retry_delay

    def analyze_single_image_with_retry(self, image_path: str, question: str) -> Optional[Dict]:
    """
    带重试机制的图片分析
    """
    for attempt in range(self.max_retries):
    try:
    result = self.analyze_single_image(image_path, question)

    if result and result.get('success'):
    return result
    elif attempt < self.max_retries – 1:
    print(f"第{attempt + 1}次尝试失败,{self.retry_delay}秒后重试…")
    time.sleep(self.retry_delay)
    else:
    return result

    except Exception as e:
    print(f"第{attempt + 1}次尝试异常:{str(e)}")
    if attempt < self.max_retries – 1:
    time.sleep(self.retry_delay)
    else:
    return {
    "success": False,
    "error": f"所有{self.max_retries}次尝试都失败:{str(e)}"
    }

    return None

    def analyze_batch_with_retry(self, image_paths: List[str], questions: List[str]) -> List[Dict]:
    """
    批量分析,每个请求都有独立的重试机制
    """
    results = []

    for i, (image_path, question) in enumerate(zip(image_paths, questions)):
    print(f"处理第 {i+1}/{len(image_paths)} 张图片…")

    result = self.analyze_single_image_with_retry(image_path, question)
    results.append(result if result else {
    "success": False,
    "error": "处理失败",
    "image_path": image_path
    })

    return results

    7. 实际应用案例

    7.1 连锁便利店库存盘点

    假设你管理着10家连锁便利店,每天需要盘点库存。传统方法需要每个店派专人花2-3小时,现在用我们的自动化系统:

    def convenience_store_inventory_chain(store_list):
    """
    连锁便利店批量库存盘点
    """
    all_store_reports = {}

    for store_id, store_info in store_list.items():
    print(f"\\n开始处理门店:{store_info['name']} (ID: {store_id})")

    # 为每个门店创建独立的输出目录
    store_output_dir = f"./inventory_reports/store_{store_id}"
    os.makedirs(store_output_dir, exist_ok=True)

    # 初始化系统
    system = InventoryAutomationSystem(
    server_url="http://localhost:7860",
    output_dir=store_output_dir
    )

    # 处理门店的各个区域
    store_report = {
    'store_id': store_id,
    'store_name': store_info['name'],
    'sections': {}
    }

    for section_name, image_folder in store_info['sections'].items():
    print(f" 处理区域:{section_name}")

    report = system.process_store_section(section_name, image_folder)
    if report:
    store_report['sections'][section_name] = report

    all_store_reports[store_id] = store_report

    # 生成门店汇总报告
    generate_store_summary(store_report)

    # 生成连锁店总报告
    generate_chain_summary(all_store_reports)

    return all_store_reports

    def generate_store_summary(store_report):
    """
    生成单个门店的汇总报告
    """
    store_id = store_report['store_id']
    store_name = store_report['store_name']

    total_products = 0
    total_issues = 0

    for section_name, report in store_report['sections'].items():
    total_products += report.get('total_products_identified', 0)
    total_issues += report.get('images_with_issues', 0)

    summary = f"""
    门店盘点报告
    ================
    门店ID:{store_id}
    门店名称:{store_name}
    盘点时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

    统计结果:
    – 识别商品总数:{total_products}
    – 发现问题数量:{total_issues}
    – 处理区域数量:{len(store_report['sections'])}

    各区域详情:
    """

    for section_name, report in store_report['sections'].items():
    summary += f"\\n{section_name}:"
    summary += f"商品{report.get('total_products_identified', 0)}个"
    summary += f",问题{report.get('images_with_issues', 0)}个"

    # 保存报告
    report_path = f"./inventory_reports/store_{store_id}/store_summary.txt"
    with open(report_path, 'w', encoding='utf-8') as f:
    f.write(summary)

    print(f"门店报告已保存:{report_path}")

    7.2 餐厅后厨库存管理

    餐厅后厨的库存管理更加复杂,需要关注食材新鲜度、存储条件等:

    def restaurant_kitchen_inventory(kitchen_sections):
    """
    餐厅后厨库存管理
    """
    system = InventoryAutomationSystem(
    server_url="http://localhost:7860",
    output_dir="./kitchen_inventory"
    )

    kitchen_report = {
    'inventory_date': datetime.now().strftime('%Y-%m-%d'),
    'sections': {},
    'alerts': []
    }

    # 定义后厨专用问题
    kitchen_questions = {
    'refrigerator': """请检查冷藏柜:
    1. 所有食材的名称和数量
    2. 食材新鲜度(通过颜色、状态判断)
    3. 储存温度是否合适(如有温度计)
    4. 是否有过期食材
    5. 清洁卫生状况""",

    'freezer': """请检查冷冻柜:
    1. 冷冻食材的名称和数量
    2. 是否有解冻迹象
    3. 霜冻厚度(如可见)
    4. 储存是否整齐有序""",

    'dry_storage': """请检查干料仓库:
    1. 所有干货的名称和数量
    2. 包装完整性
    3. 是否有虫害迹象
    4. 储存条件(避光、防潮)""",

    'prep_area': """请检查准备区:
    1. 正在准备的食材
    2. 工具清洁状况
    3. 操作台卫生
    4. 人员操作规范(如可见)"""
    }

    for section_name, image_folder in kitchen_sections.items():
    print(f"\\n检查后厨区域:{section_name}")

    # 获取该区域的问题
    question = kitchen_questions.get(section_name,
    InventoryQuestions.get_product_identification_question())

    # 处理该区域的所有图片
    image_files = list(Path(image_folder).glob("*.jpg")) + \\
    list(Path(image_folder).glob("*.png"))

    if not image_files:
    print(f" 未找到图片:{image_folder}")
    continue

    section_results = []
    for img_path in image_files:
    result = system.client.analyze_single_image(str(img_path), question)
    if result and result.get('success'):
    section_results.append(result)

    # 检查是否有警报项
    response_text = result['response'].lower()
    alert_keywords = ['过期', '变质', '不新鲜', '虫害', '脏乱', '违规']

    for keyword in alert_keywords:
    if keyword in response_text:
    kitchen_report['alerts'].append({
    'section': section_name,
    'image': str(img_path),
    'issue': keyword,
    'time': datetime.now().strftime('%H:%M:%S')
    })
    print(f" 发现问题:{section_name} – {keyword}")

    kitchen_report['sections'][section_name] = {
    'image_count': len(image_files),
    'success_count': len(section_results),
    'alert_count': len([a for a in kitchen_report['alerts']
    if a['section'] == section_name])
    }

    # 生成后厨库存报告
    generate_kitchen_report(kitchen_report)

    return kitchen_report

    8. 总结

    通过这篇文章,我们完整地构建了一个基于Ostrakon-VL-8B的库存盘点自动化系统。让我们回顾一下关键要点:

    8.1 核心收获

  • 技术掌握:学会了如何通过Python调用Ostrakon-VL-8B的API,实现了从单张图片分析到批量处理的完整流程。

  • 系统搭建:构建了一个完整的库存盘点自动化系统,包括图片预处理、API调用、结果解析、报告生成等模块。

  • 实用技巧:掌握了提升识别准确率的拍摄技巧、问题优化策略,以及性能优化和错误处理的方法。

  • 扩展应用:看到了系统在连锁便利店和餐厅后厨等不同场景下的实际应用案例。

  • 8.2 实际价值

    这个自动化系统带来的实际价值是显而易见的:

    • 效率提升:传统手动盘点需要数小时的工作,现在几分钟就能完成
    • 准确性提高:减少人为错误,确保库存数据的准确性
    • 成本降低:减少人工盘点所需的时间和人力成本
    • 实时监控:可以随时进行库存检查,及时发现和解决问题
    • 数据积累:自动生成电子化报告,便于数据分析和历史对比

    8.3 下一步建议

    如果你已经成功部署了这个系统,可以考虑以下几个方向的进一步优化:

  • 集成现有系统:将盘点结果自动导入到你的ERP或库存管理系统中
  • 移动端应用:开发手机APP,让员工可以直接用手机拍照上传
  • 实时告警:设置阈值,当库存低于安全水平时自动发送告警
  • 预测分析:基于历史盘点数据,预测未来的库存需求
  • 多模型融合:结合其他视觉模型,提升特定商品的识别准确率
  • 8.4 开始行动

    现在就开始尝试吧!从最简单的单张图片分析开始,逐步扩展到整个货架、整个店铺。记住几个关键点:

  • 从小处着手:先测试一个货架,确保一切工作正常
  • 优化拍摄质量:好的输入才能得到好的输出
  • 逐步扩展:成功后再扩展到更多区域和门店
  • 持续优化:根据实际使用情况调整问题和参数
  • 库存盘点不再需要繁琐的手工操作,也不再容易出错。通过Ostrakon-VL-8B和Python自动化,你可以把宝贵的时间用在更有价值的工作上,让技术为你服务。


    获取更多AI镜像

    想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

    赞(0)
    未经允许不得转载:171主机测评 » Ostrakon-VL-8B基础教程:Python调用API实现批量库存盘点自动化
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址