当前位置: 首页 > news >正文

『大模型部署』NVIDIA Orin + bnb量化 + Qwen3-VL | 4bit、8bit量化

本文介绍了如何使用BitsAndBytes库,在NVIDIA Orin上,对Qwen3-VL多模态模型进行量化部署。

BitsAndBytes是一个轻量级PyTorch库,支持8/4位量化技术,能将模型显存占用降低90%以上。

文章详细讲解了从环境搭建到模型量化、再到实际推理的全流程,包括:

  1. 在NVIDIA Orin上安装PyTorch环境和量化依赖库
  2. 使用LLaMA-Factory进行LoRA微调
  3. 合并基础模型和LoRA适配器权重
  4. 实施8位和4位量化(包含NF4/FP4量化类型选择)
  5. 提供多个应用案例:图像描述生成、指定任意物体检测等

量化后的模型在保持较高精度前提下,显存占用大幅降低(8位量化4.6G,4位量化2.7G),使得大模型能在边缘设备上高效运行。

文中还展示了量化模型在NVIDIA Orin上的实际推理效果和资源占用情况。

万字长文,需耐心观看~

目录

基础内容

1、推理环境搭建

2、Qwen3-VL+ NVIDIA Orin 推理——8bit量化版本

 2.1对Qwen3-VL进行Lora 监督微调

2.2、合并权重

2.3、使用bnb进行8bit量化 

2.4、8bit量化后的模型推理(实践案例1)

2.5、8bit量化后的模型推理(实践案例2)

3、Qwen3-VL+ NVIDIA Orin 推理——4bit量化版本

3.1、对Qwen3-VL进行Lora 监督微调

3.2、合并权重

3.3、使用bnb进行4bit量化 

3.4、量化后的模型推理(实践案例)


基础内容

bnb量化,全称是bitsandbytes,为什么要用它?

bitsandbytes是一个轻量级 PyTorch 库,专为深度学习模型提供k-bit 量化技术,通过将模型权重和优化器状态从 32/16 位压缩至 8/4 位,显著减少内存占用 (最高 90%),同时保持几乎相同的模型性能。

核心特点:

  • 降低硬件门槛:使普通显卡也能运行训练千亿参数大模型
  • 无缝集成 PyTorch:仅需几行代码修改,不改变原有训练流程
  • 支持多种量化策略8-bit 优化器LLM.int8 () 推理量化4-bit QLoRA 微调
  • 多硬件兼容NVIDIA GPU、AMD ROCm、Intel XPU、Apple MPS 等

关键依赖库:
torch                     2.6.0
torchaudio                2.6.0
torchtyping               0.1.5
torchvision               0.21.0
transformers              4.57.1
bitsandbytes              0.49.0.dev0基础依赖库:
scikit-image              0.24.0
scikit-learn              1.6.1
numpy                     1.23.4
opencv-python             4.11.0.86
pillow                    11.1.0
transforms3d              0.3.1

1、推理环境搭建

1)在NVIDIA Orin 中安装torch 2.6.0,参考我这篇博客:

NVIDIA Jetson 环境安装指导 PyTorch | Conda | cudnn

2)安装相关依赖库

编辑一个 requirements.txt,内容如下:

transformers==4.57.1
scikit-image==0.24.0
scikit-learn==1.6.1
numpy==1.23.4
opencv-python==4.11.0.86
pillow==11.1.0
transforms3d==0.3.1

执行命令进行安装:

pip install -r requirements.txt

3)安装bitsandbytes

由于是在NVIDIA Orin中,我们使用源码编译方式,进行安装。

git clone https://github.com/bitsandbytes-foundation/bitsandbytes.git && cd bitsandbytes/
cmake -DCOMPUTE_BACKEND=cuda -S .
make
pip install -e .  

执行cmake -DCOMPUTE_BACKEND=cuda -S .时,打印信息:


-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring bitsandbytes (Backend: cuda)
-- The CUDA compiler identification is NVIDIA 12.6.68
-- Detecting CUDA compiler ABI info
-- Detecting CUDA compiler ABI info - done
-- Check for working CUDA compiler: /usr/local/cuda/bin/nvcc - skipped
-- Detecting CUDA compile features
-- Detecting CUDA compile features - done
-- Found CUDAToolkit: /usr/local/cuda/include (found version "12.6.68") 
-- Looking for C++ include pthread.h
-- Looking for C++ include pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE  
-- CUDA Version: 126 (12.6.68)
-- CUDA Compiler: /usr/local/cuda/bin/nvcc
-- CMake < 3.23.0; determining CUDA architectures supported...
-- CUDA Capabilities Available: 50;52;53;60;61;62;70;72;75;80;86;87;89;90
-- CUDA Capabilities  Selected: 50;52;53;60;61;62;70;72;75;80;86;87;89;90
-- CUDA Targets: 50-real;52-real;53-real;60-real;61-real;62-real;70-real;72-real;75-real;80-real;86-real;87-real;89-real;90
-- CUDA NVCC Flags:  --use_fast_math
-- Configuring done
-- Generating done
-- Build files have been written to: /home/byd/2025/bitsandbytes

执行make,进行编译时,打印信息:

[ 14%] Building CXX object CMakeFiles/bitsandbytes.dir/csrc/common.cpp.o[ 28%] Building CXX object CMakeFiles/bitsandbytes.dir/csrc/cpu_ops.cpp.o
[ 42%] Building CXX object CMakeFiles/bitsandbytes.dir/csrc/pythonInterface.cpp.o
[ 57%] Building CUDA object CMakeFiles/bitsandbytes.dir/csrc/ops.cu.o
[ 71%] Building CUDA object CMakeFiles/bitsandbytes.dir/csrc/kernels.cu.o
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
ptxas warning : Value of threads per SM for entry _Z9kQuantizePfS_Phi is out of range. .minnctapersm will be ignored
[ 85%] Linking CUDA device code CMakeFiles/bitsandbytes.dir/cmake_device_link.o
[100%] Linking CXX shared library bitsandbytes/libbitsandbytes_cuda126.so
[100%] Built target bitsandbytes

最后执行pip install -e .进行安装,打印信息:


.....
Building wheels for collected packages: bitsandbytesBuilding editable for bitsandbytes (pyproject.toml) ... doneCreated wheel for bitsandbytes: filename=bitsandbytes-0.49.0.dev0-0.editable-cp310-cp310-linux_aarch64.whl size=6975 sha256=322943120b81314fd0891b079dd84d225c803db267e2f256de2eb7b3f48f5acaStored in directory: /tmp/pip-ephem-wheel-cache-b93_rfxb/wheels/dc/8b/31/ae7b8434a1a8f35b69dab355573ddf62b93fb625e58acb4640
Successfully built bitsandbytes
Installing collected packages: bitsandbytes
Successfully installed bitsandbytes-0.49.0.dev0

编写一个代码,测试使用能使用CUDA加速:


import torch
import bitsandbytes as bnb
import sysprint("=" * 60)
print("bitsandbytes CUDA 详细检测")
print("=" * 60)# 1. 检查系统环境
print("1. 系统环境检查:")
print(f"   Python 版本: {sys.version}")
print(f"   PyTorch 版本: {torch.__version__}")
print(f"   bitsandbytes 版本: {bnb.__version__}")# 2. 检查 PyTorch 的 CUDA 支持
print("\n2. PyTorch CUDA 支持:")
print(f"   torch.cuda.is_available(): {torch.cuda.is_available()}")
if torch.cuda.is_available():print(f"   CUDA 版本: {torch.version.cuda}")print(f"   当前设备: {torch.cuda.current_device()}")print(f"   设备名称: {torch.cuda.get_device_name()}")print(f"   GPU 数量: {torch.cuda.device_count()}")
else:print("   ❌ PyTorch 无法访问 CUDA")# 3. 检查 bitsandbytes 的具体模块
print("\n3. bitsandbytes 模块检查:")
modules_to_check = ['optim', 'functional', 'nn']
for module in modules_to_check:has_module = hasattr(bnb, module)status = "✓" if has_module else "✗"print(f"   {status} bnb.{module}: {has_module}")# 4. 尝试导入具体功能
print("\n4. 功能导入测试:")
try:from bitsandbytes import optim, functional, nnprint("   ✓ 核心模块导入成功")# 检查优化器if hasattr(optim, 'Adam8bit'):print("   ✓ Adam8bit 优化器可用")if hasattr(optim, 'Adam32bit'):print("   ✓ Adam32bit 优化器可用")except ImportError as e:print(f"   ❌ 模块导入失败: {e}")# 5. 实际功能测试
print("\n5. 实际功能测试:")
if torch.cuda.is_available():try:# 创建测试模型和数据model = torch.nn.Linear(10, 5)input_data = torch.randn(2, 10)# 测试 GPU 转移model = model.cuda()input_data = input_data.cuda()print("   ✓ 模型和数据成功转移到 GPU")# 测试 8-bit 优化器optimizer = bnb.optim.Adam8bit(model.parameters(), lr=0.001)print("   ✓ 8-bit 优化器创建成功")# 前向传播output = model(input_data)print("   ✓ GPU 上前向传播成功")# 反向传播loss = output.sum()loss.backward()print("   ✓ 反向传播成功")# 优化器步骤optimizer.step()print("   ✓ 优化器步骤执行成功")print("\n🎉 所有 CUDA 功能测试通过!bitsandbytes 可以使用 CUDA 加速")except Exception as e:print(f"   ❌ CUDA 功能测试失败: {e}")print(f"   错误类型: {type(e).__name__}")
else:print("   ⚠ 跳过 CUDA 功能测试(PyTorch CUDA 不可用)")print("=" * 60)

运行信息:

============================================================
bitsandbytes CUDA 详细检测
============================================================
1. 系统环境检查:
   Python 版本: 3.10.16 | packaged by conda-forge | (main, Apr  3 2025, 14:16:18) [GCC 13.3.0]
   PyTorch 版本: 2.6.0
   bitsandbytes 版本: 0.49.0.dev0

2. PyTorch CUDA 支持:
   torch.cuda.is_available(): True
   CUDA 版本: 12.6
   当前设备: 0
   设备名称: Orin
   GPU 数量: 1

3. bitsandbytes 模块检查:
   ✓ bnb.optim: True
   ✓ bnb.functional: True
   ✓ bnb.nn: True

4. 功能导入测试:
   ✓ 核心模块导入成功
   ✓ Adam8bit 优化器可用
   ✓ Adam32bit 优化器可用

5. 实际功能测试:
   ✓ 模型和数据成功转移到 GPU
   ✓ 8-bit 优化器创建成功
   ✓ GPU 上前向传播成功
   ✓ 反向传播成功
   ✓ 优化器步骤执行成功

🎉 所有 CUDA 功能测试通过!bitsandbytes 可以使用 CUDA 加速
============================================================

2、Qwen3-VL+ NVIDIA Orin 推理——8bit量化版本

  • 首先使用LLaMA Factory,对Qwen3-VL-4b进行Lora监督微调,得到微调后的lora权重;
  • 然后合并 基础Qwen3-VL-4b权重 + lora权重 = 完整微调权重
  • 最后对“完整微调权重”进行8bit量化,进行模型推理,提供实践案例。

 2.1对Qwen3-VL进行Lora 监督微调

下面是LLaMA Factory 的微调训练配置,用于Qwen3-VL-4B-Instruct 多模态模型 的有监督微调:

配置值作用 & 分析
模型Qwen3-VL-4B-Instruct(HuggingFace 源)选择了 4B 参量的多模态(图文)模型,适合轻量化多模态任务微调
微调方法LoRA低参数量微调(只训练适配器权重),显存占用低、训练速度快
量化策略8bit(QLoRA)+ bnb 量化进一步压缩显存(8bit 加载模型),结合 LoRA 可在普通消费级显卡(如 16G 显存)上训练
训练阶段

Supervised Fine-Tuning

(SFT,有监督微调)

基于标注数据集训练模型生成符合要求的输出

微调界面:

核心超参数配置(仅供参考,需要根据自己的模型调整的)

配置值作用 & 分析
学习率3e-5LoRA 微调的常用学习率(比全量微调高 1-2 个数量级)
训练轮数3.0训练轮次较少,适合快速收敛或小数据集(避免过拟合)
批处理 / 梯度累积批大小 2 + 梯度累积 2 → 等效批大小 4小批量 + 梯度累积平衡显存占用与训练稳定性
截断长度2048匹配 Qwen3-VL 的上下文长度上限,适配长文本 / 多模态输入
学习率调节器cosine(余弦退火)训练后期平滑降低学习率,帮助模型稳定收敛
计算类型bf16半精度计算(需显卡支持 Ampere 及以上架构),进一步节省显存并加速训练

微调完整后,得到lora权重,默认保存在LLaMA Factory目录下的./saves/Qwen3-VL-4B-Instruct/lora/xxxxxx

2.2、合并权重

这里的思路:基础Qwen3-VL-4b权重 + lora权重 = 完整微调权重

我们只需确定:基础权重的路径model_name_or_path、lora微调权重路径adapter_name_or_path

然后执行下面命令,进行合并:

llamafactory-cli export \--model_name_or_path "/home/user/.cache/huggingface/hub/models--Qwen--Qwen3-VL-4B-Instruct/snapshots/ebb281ec70b05090aa6165b016eac8ec08e71b17/" \--adapter_name_or_path ./saves/Qwen3-VL-4B-Instruct/lora/train_2025-11-13-13-37/ \--export_dir ./qwen3-vl-4b-merged-20251113-fp16-lora\--template qwen3_vl_nothink

等待合并完成~

合并后的权重大小:

8.3G    model_path/qwen3-vl-4b-merged

2.3、使用bnb进行8bit量化 

通过BitsAndBytes工具Qwen3-VL 模型进行 8 位量化,以降低模型显存占用,

同时保存量化后的模型、分词器(tokenizer)和处理器(processor),便于后续部署使用。

  • 使用transformers库中的模型加载类(Qwen3VLForConditionalGenerationAutoModel)、量化配置(BitsAndBytesConfig
  • 依赖torch进行张量计算和设备管理。

模型量化的实力代码,如下所示:

from transformers import Qwen3VLForConditionalGeneration, AutoModel, BitsAndBytesConfig, AutoTokenizer, AutoProcessor
import torchinput_merged_model = "./model_path/qwen3-vl-4b-merged-20251113-8bit-lora"
output_merged_model = "./model_path/qwen3-vl-4b-bnb-quantized-8bit-merged-20251113-int8"'''
BitsAndBytes 仅支持:位量化: quant_type="fp4" 或 "nf4"(配合 load_in_4bit=True)位量化:必须用 quant_type="int8"(实际是 8 位整数,不是浮点)。fp8 是 NVIDIA 的 8 位浮点格式,不在 Hugging Face BitsAndBytes 支持范围内。
'''def quantize_model():# 配置8位bnb量化quantization_config = BitsAndBytesConfig(load_in_8bit=True,  # 8位量化(必须)bnb_8bit_compute_dtype=torch.float16,  # 计算用float16bnb_8bit_use_double_quant=True,  # 双量化(压缩高效,可选)bnb_8bit_quant_type="int8"  # ✅ 修正:必须为 "int8"(8位整数量化))try:# 尝试使用特定模型类print("尝试使用 Qwen3VLForConditionalGeneration 加载模型...")model = Qwen3VLForConditionalGeneration.from_pretrained(input_merged_model,quantization_config=quantization_config,device_map="auto",trust_remote_code=True)except Exception as e:print(f"使用特定类失败: {e}")print("尝试使用 AutoModel 加载模型...")# 回退到 AutoModelmodel = AutoModel.from_pretrained(input_merged_model,quantization_config=quantization_config,device_map="auto",trust_remote_code=True)# 加载并保存tokenizer和processortokenizer = AutoTokenizer.from_pretrained(input_merged_model, trust_remote_code=True)processor = AutoProcessor.from_pretrained(input_merged_model, trust_remote_code=True)# 保存量化后的模型和相关组件model.save_pretrained(output_merged_model)tokenizer.save_pretrained(output_merged_model)processor.save_pretrained(output_merged_model)print("模型8位量化并保存完成!")if __name__ == "__main__":quantize_model()

明确 8 位量化的关键参数:

  • load_in_8bit=True:启用 8 位量化
  • bnb_8bit_quant_type="int8":指定量化类型为 8 位整数(符合 BitsAndBytes 对 8 位量化的要求)
  • bnb_8bit_compute_dtype=torch.float16:量化过程中使用 float16 进行计算,平衡精度与性能
  • bnb_8bit_use_double_quant=True:启用双量化(可选优化,进一步压缩模型大小,减少内存占用)

量化后的权重大小:

4.6G    model_path/qwen3-vl-4b-bnb-quantized-8bit-merged-20251113-int8

2.4、8bit量化后的模型推理(实践案例1)

这个实践案例,基于 Qwen3-VL 量化模型的单张图像描述

  • 模型加载:使用AutoModelForImageTextToText加载多模态模型,适配 Qwen3-VL 的图文生成能力。
  • 核心参数:device_map="auto"自动分配模型到 CPU/GPU,trust_remote_code=True允许加载模型自定义代码。
  • 处理器配套:AutoProcessor用于统一处理文本提示和图像输入,确保格式符合模型要求。

特点:

  • 输入处理:用apply_chat_template格式化对话(用户 + 图像 + 文本提示),processor将文本和图像转为模型可识别的张量,并移至对应设备。
  • 生成配置:max_new_tokens=512限制描述长度,do_sample=False+num_beams=1采用确定性生成,保证结果稳定。
  • 结果提取:裁剪模型输出中冗余的输入部分,解码后去除特殊 token,得到纯净描述文本。

推理代码:

import os
from PIL import Image
import torch
from transformers import AutoProcessor, AutoModelForImageTextToText# 模型路径配置
QUANTIZED_MODEL_PATH = "./model_path/qwen3-vl-4b-bnb-quantized-8bit-merged-20251113-int8"
# 输入图片路径
input_image = "dataset/test_pic/350012.jpg"# 初始化模型和处理器
print(f"正在加载模型:{QUANTIZED_MODEL_PATH}...")
model = AutoModelForImageTextToText.from_pretrained(QUANTIZED_MODEL_PATH,device_map="auto",trust_remote_code=True
)
processor = AutoProcessor.from_pretrained(QUANTIZED_MODEL_PATH)
print("模型和处理器加载成功!")def describe_image(image_path):"""生成图像的详细描述"""try:# 加载图像image = Image.open(image_path).convert("RGB")# 构建提示词(专注于图像整体描述)messages = [{"role": "user","content": [{"type": "image"},{"type": "text", "text": "请详细描述这张图片的内容,包括场景、物体、颜色、布局等信息。"}]}]# 处理输入text = processor.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)inputs = processor(text=text,images=image,return_tensors="pt",padding=True).to(model.device)# 生成描述with torch.no_grad():generated_ids = model.generate(**inputs,max_new_tokens=512,do_sample=False,num_beams=1)# 提取并返回生成的描述generated_ids_trimmed = generated_ids[:, inputs.input_ids.shape[1]:]description = processor.batch_decode(generated_ids_trimmed,skip_special_tokens=True,clean_up_tokenization_spaces=False)[0]return descriptionexcept Exception as e:print(f"图像描述失败: {str(e)}")return ""def process_single_image(image_path):"""处理单张图像并打印描述"""if not os.path.exists(image_path):print(f"图像文件不存在: {image_path}")returnprint(f"\n--- 处理图像: {os.path.basename(image_path)} ---")description = describe_image(image_path)if description:print("\n图像描述:")print(description)print(f"\n--- 处理完成 ---")if __name__ == "__main__":import argparseparser = argparse.ArgumentParser(description='图像描述工具')parser.add_argument('--image', type=str, default=input_image, help='需要描述的图像路径')args = parser.parse_args()process_single_image(args.image)

输入图片:

运行信息:

正在加载模型:./model_path/qwen3-vl-4b-bnb-quantized-8bit-merged-20251113-int8...
模型和处理器加载成功!--- 处理图像: 350012.jpg ---
The following generation flags are not valid and may be ignored: ['temperature', 'top_p', 'top_k']. Set `TRANSFORMERS_VERBOSITY=info` for more details.图像描述:
这张图片展示了一个布置温馨、光线柔和的现代客厅。整个空间以中性色调为主,搭配暖色装饰,营造出舒适宜人的氛围。**场景与布局:**
客厅布局清晰,以一张米色布艺沙发为中心,沙发靠墙放置,旁边是深色木质边桌。前方是一个深棕色木质咖啡桌,桌下铺着一块色彩丰富的几何图案地毯,地毯以蓝色、红色和米色为主,为整个空间增添活力。左侧是壁炉区域,右侧是边桌和台灯,墙上挂有艺术画作,整体布局平衡且富有层次感。**主要物体与细节:**1. **沙发:**- 一张米色布艺沙发,靠背和坐垫厚实,显得柔软舒适。- 沙发上摆放着多个靠垫,颜色丰富:深紫色、橙色、红色和橄榄绿,形成视觉焦点。- 沙发右侧有一个深色木质边桌,上面放着一盏白色台灯和一个酒杯。2. **咖啡桌:**- 深棕色木质咖啡桌,带有开放式下层置物架。- 桌面上摆放着一个白色陶瓷花瓶,瓶身呈优雅的鹅颈状,旁边是一个透明玻璃花瓶,内有干花枝。- 一个小型绿色盆栽植物放在花瓶旁,增添自然气息。- 一个透明玻璃杯和一个酒杯也放在桌上。3. **壁炉与镜子:**- 左侧是一个深色砖砌壁炉,壁炉上方悬挂着一个大尺寸的木框镜子,镜中反射出楼梯的一部分,暗示房屋结构。- 壁炉上方的架子上摆放着几个玻璃烛台和装饰品。4. **墙面装饰:**- 墙上挂着一幅大型抽象画,画中描绘了城市景观,色调以暖橙、赭石和米白为主,与沙发靠垫颜色相呼应。- 墙角处有一扇白色窗框的小窗,透入自然光。5. **灯光与家具:**- 墙角的台灯和壁炉旁的台灯都采用白色灯罩,与整体色调协调。- 墙边有一个白色书架,上面摆放着书籍和装饰品。**颜色与氛围:**
整个空间以米--- 处理完成 ---

2.5、8bit量化后的模型推理(实践案例2)

这个模型推理示例,是 Qwen3-VL 量化模型的特定类别物体检测与可视化

核心用于批量处理图像提取目标物体多维度信息生成可视化结果

核心功能:

  • 支持指定任意类别的物体(table、cup、bottle 等),输出类别、边界框、颜色、形状、外观 5 类信息。
  • 支持批量读取图像目录,自动按自然排序处理文件。
  • 生成带边界框和类别的可视化图像,同时打印详细物体描述。
  • 包含异常处理和格式容错,确保流程稳定性。

推理代码:

import os
import json
import glob
import re
import torch
import numpy as np
from PIL import Image
import cv2
import time
from transformers import AutoProcessor, AutoModelForImageTextToTextQUANTIZED_MODEL_PATH = "./model_path/qwen3-vl-4b-bnb-quantized-8bit-merged-20251113-int8" # lora微调后的8-bit量化模型
input_dir = "./dataset_label/01_waitingroom/"
output_dir = "./Output_DATA/01_waitingroom"# 指定检测的物体类别,可以指定任意物体名称或在物体描述
NODE_SPACE = ['table', 'cup', 'bottle', 'chair', 'robot', 'garbage can', 'shelf', 'tissue box', 'potted plant']# 全局初始化模型和处理器
print(f"正在加载8-bit量化后的Qwen3-VL-4B模型:{QUANTIZED_MODEL_PATH}...")
model = AutoModelForImageTextToText.from_pretrained(QUANTIZED_MODEL_PATH,device_map="auto",trust_remote_code=True
)
processor = AutoProcessor.from_pretrained(QUANTIZED_MODEL_PATH)
print("修复后的量化Qwen3-VL-4B模型和处理器加载成功!")def call_vlm(image_path, prompt):"""安全的VLM调用函数"""try:# >>>>> 修改点1:直接使用 PIL 加载图像 <<<<<# 假设模型能处理任意大小的有效图像,不再强制调整尺寸image = Image.open(image_path).convert("RGB") # 确保是 RGB 模式messages = [{"role": "user","content": [{"type": "image"},{"type": "text", "text": prompt}]}]text = processor.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)inputs = processor(text=text,images=image,return_tensors="pt",padding=True).to(model.device)# 生成参数with torch.no_grad():generated_ids = model.generate(**inputs,max_new_tokens=512,do_sample=False,num_beams=1,early_stopping=False)generated_ids_trimmed = generated_ids[:, inputs.input_ids.shape[1]:]output_text = processor.batch_decode(generated_ids_trimmed,skip_special_tokens=True,clean_up_tokenization_spaces=False)[0]return output_textexcept Exception as e:print(f"VLM调用失败: {str(e)}")return ""def detect_and_describe_objects(image_path):"""检测图像中的物体,获取其描述并反归一化bbox"""# 构建提示词prompt = f"""请严格检测图像中属于以下类别的所有物体:{NODE_SPACE}。对于每个检测到的物体,请提供:1. 类别 (category)2. 紧贴边缘的边界框 (bbox),格式为 [x1,y1,x2,y2](整数像素坐标)3. 颜色 (color)4. 形状 (shape)5. 外观描述 (appearance)输出要求:- 仅识别列表中的类别。- bbox必须精确且紧密贴合物体边缘。- 描述信息需与bbox内的区域一致。- 严格以 JSON 数组形式返回,不要包含任何额外的文本或代码块标记。输出示例(格式参考):[{{"category": "cup","bbox": [97, 203, 176, 282],"color": "白色","shape": "圆柱形","appearance": "带把手陶瓷杯"}},{{"category": "table","bbox": [10, 318, 639, 474],"color": "原木色","shape": "长方形","appearance": "四腿木质桌面"}}]"""try:# >>>>> 修改点2:直接使用 PIL 获取图像尺寸 <<<<<# 加载图像以获取尺寸用于坐标反归一化with Image.open(image_path) as img_pil:w_img, h_img = img_pil.convert("RGB").size # 确保获取到尺寸print("开始调用VLM进行物体检测...")time_start = time.time()response = call_vlm(image_path, prompt)time_end = time.time()print(f'VLM推理时间:{time_end - time_start:.2f}s')if not response:print("VLM返回空响应")return []# 解析JSON响应try:objects_data = json.loads(response.strip())except json.JSONDecodeError:# 尝试清理常见的格式问题cleaned_response = response.strip()if cleaned_response.startswith('```json'):cleaned_response = cleaned_response[7:]if cleaned_response.startswith('```'):cleaned_response = cleaned_response[3:]if cleaned_response.endswith('```'):cleaned_response = cleaned_response[:-3]cleaned_response = cleaned_response.strip()try:objects_data = json.loads(cleaned_response)except json.JSONDecodeError as e:print(f"JSON解析失败: {e}")print(f"原始响应内容: {response}")return []if not isinstance(objects_data, list):print(f"响应不是列表格式: {objects_data}")return []# 处理并验证每个检测到的物体valid_objects = []for i, obj in enumerate(objects_data):# 验证基本结构if not (isinstance(obj, dict) and obj.get('category') in NODE_SPACE and len(obj.get('bbox', [])) == 4):print(f"警告:跳过无效的检测结果 #{i+1}: {obj}")continue# 提取并转换bbox坐标try:coords = list(map(float, obj['bbox']))x1_norm, y1_norm, x2_norm, y2_norm = coordsexcept (ValueError, TypeError) as e:print(f"警告:跳过坐标格式错误的物体 #{i+1}: {e}")continue# 反归一化 (假设模型输出的是0-1000范围的坐标)x1 = int(round(x1_norm / 1000 * w_img))y1 = int(round(y1_norm / 1000 * h_img))x2 = int(round(x2_norm / 1000 * w_img))y2 = int(round(y2_norm / 1000 * h_img))# 确保坐标在图像范围内x1 = max(0, min(x1, w_img - 1))y1 = max(0, min(y1, h_img - 1))x2 = max(x1 + 1, min(x2, w_img - 1))y2 = max(y1 + 1, min(y2, h_img - 1))# 更新对象字典obj['bbox'] = [x1, y1, x2, y2]valid_objects.append(obj)print(f"共检测并验证了 {len(valid_objects)} 个物体。")return valid_objectsexcept Exception as e:print(f"物体检测过程失败: {str(e)}")return []def visualize_detections(image_path, detected_objects, output_path):"""可视化检测结果:绘制bbox和标签"""try:# 读取图像image_bgr = cv2.imread(image_path)if image_bgr is None:print(f"无法读取图像: {image_path}")returnh_img, w_img = image_bgr.shape[:2] # 获取OpenCV图像的尺寸# 绘制每个检测到的物体for i, obj in enumerate(detected_objects):bbox = obj.get('bbox', [0, 0, 0, 0])category = obj.get('category', 'Unknown')# 确保坐标在有效范围内 (基于OpenCV图像尺寸)x1, y1, x2, y2 = bboxx1 = max(0, min(x1, w_img - 1))y1 = max(0, min(y1, h_img - 1))x2 = max(x1 + 1, min(x2, w_img - 1))y2 = max(y1 + 1, min(y2, h_img - 1))# 绘制边界框cv2.rectangle(image_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2)# 准备标签文本label_parts = [category]label = " ".join(label_parts)# 计算标签位置(在框上方)(text_width, text_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)y_label = y1 - 10 if y1 - 10 > 10 else y1 + 10 + text_height# 绘制标签背景矩形cv2.rectangle(image_bgr, (x1, y_label - text_height - baseline), (x1 + text_width, y_label + baseline), (0, 255, 0), -1)# 绘制标签文字cv2.putText(image_bgr, label, (x1, y_label),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)# 保存可视化图像cv2.imwrite(output_path, image_bgr)print(f"检测结果可视化已保存至: {output_path}")except Exception as e:print(f"可视化检测结果失败: {str(e)}")def print_object_descriptions(detected_objects):"""打印每个检测到的物体的详细描述信息"""if not detected_objects:print("未检测到任何物体。")returnprint("\n--- 检测到的物体描述 ---")for i, obj in enumerate(detected_objects):print(f"物体 #{i+1}:")print(f"  类别 (Category): {obj.get('category', 'N/A')}")print(f"  边界框 (Bbox): {obj.get('bbox', 'N/A')}")print(f"  颜色 (Color): {obj.get('color', 'N/A')}")print(f"  形状 (Shape): {obj.get('shape', 'N/A')}")print(f"  外观 (Appearance): {obj.get('appearance', 'N/A')}")print("-" * 20)print("--- 描述结束 ---\n")def natural_sort_key(filename):"""自然排序含数字的文件名"""return [int(t) if t.isdigit() else t.lower()for t in re.split(r'(\d+)', os.path.basename(filename))]def process_images_simple(input_dir, output_dir):"""批量处理图像目录,执行简化任务"""try:os.makedirs(output_dir, exist_ok=True)viz_output_dir = os.path.join(output_dir, "visualizations_simple")os.makedirs(viz_output_dir, exist_ok=True)image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.gif']image_files = []for ext in image_extensions:image_files.extend(glob.glob(os.path.join(input_dir, ext)))image_files = sorted(image_files, key=natural_sort_key)if not image_files:print(f"在目录 {input_dir} 中未找到图像文件")returnfor image_path in image_files:image_name = os.path.basename(image_path)print(f"\n---------- 处理图像: {image_name} ----------")# 1. 检测物体并获取描述detected_objects = detect_and_describe_objects(image_path)# 2. 打印物体描述print_object_descriptions(detected_objects)# 3. 可视化检测框viz_filename = os.path.splitext(image_name)[0] + "_simple_viz.jpg"viz_path = os.path.join(viz_output_dir, viz_filename)visualize_detections(image_path, detected_objects, viz_path)print(f"---------- 完成处理: {image_name} ----------\n")print("\n所有图像处理完成")except Exception as e:print(f"处理图像目录失败: {str(e)}")if __name__ == "__main__":import argparseparser = argparse.ArgumentParser(description='简化版Qwen3-VL物体检测与可视化工具')parser.add_argument('--input_dir', type=str, default=input_dir, help='包含图像的输入目录')parser.add_argument('--output_dir', type=str, default=output_dir, help='输出结果的目录')args = parser.parse_args()process_images_simple(args.input_dir, args.output_dir)

初始化模型→2. 批量读取图像→3. 单图处理(检测物体→解析结果→打印描述→绘制可视化)→4. 输出所有结果。

核心逻辑是利用多模态模型实现特定类别物体的检测与描述,并通过可视化和文本输出直观呈现结果

运行效果:

打印信息:

---------- 处理图像: be158920.jpg ----------
开始调用VLM进行物体检测...
VLM推理时间:17.25s
共检测并验证了 2 个物体。

--- 检测到的物体描述 ---
物体 #1:
  类别 (Category): table
  边界框 (Bbox): [162, 304, 315, 401]
  颜色 (Color): 原木色
  形状 (Shape): 长方形
  外观 (Appearance): 木质桌面,表面有纹理
--------------------
物体 #2:
  类别 (Category): potted plant
  边界框 (Bbox): [402, 148, 495, 256]
  颜色 (Color): 绿色
  形状 (Shape): 灌木状
  外观 (Appearance): 高大的盆栽植物,叶片茂密
--------------------
--- 描述结束 ---

检测结果可视化已保存至: ./Output_DATA/01_waitingroom/visualizations_simple/be158920_simple_viz.jpg
---------- 完成处理: be158920.jpg ----------

其他运行示例:

3、Qwen3-VL+ NVIDIA Orin 推理——4bit量化版本

思路其实和上面的8bit量化基本一致的,只是代码参数有差异;

  • 首先使用LLaMA Factory,对Qwen3-VL-4b进行Lora监督微调,得到微调后的lora权重;
  • 然后合并 基础Qwen3-VL-4b权重 + lora权重 = 完整微调权重
  • 最后对“完整微调权重”进行4bit量化,进行模型推理,计算模型准确率和召回率。

3.1、对Qwen3-VL进行Lora 监督微调

下面是LLaMA Factory 的微调训练配置,用于Qwen3-VL-4B-Instruct 多模态模型 的有监督微调:

配置值作用 & 分析
模型Qwen3-VL-4B-Instruct(HuggingFace 源)选择了 4B 参量的多模态(图文)模型,适合轻量化多模态任务微调
微调方法LoRA低参数量微调(只训练适配器权重),显存占用低、训练速度快
量化策略4bit(QLoRA)+ bnb 量化进一步压缩显存(4bit 加载模型),结合 LoRA 可在普通消费级显卡(如 16G 显存)上训练
训练阶段

Supervised Fine-Tuning

(SFT,有监督微调)

基于标注数据集训练模型生成符合要求的输出

微调界面:

核心超参数配置(仅供参考,需要根据自己的模型调整的)

配置值作用 & 分析
学习率3e-5LoRA 微调的常用学习率(比全量微调高 1-2 个数量级)
训练轮数3.0训练轮次较少,适合快速收敛或小数据集(避免过拟合)
批处理 / 梯度累积批大小 2 + 梯度累积 2 → 等效批大小 4小批量 + 梯度累积平衡显存占用与训练稳定性
截断长度2048匹配 Qwen3-VL 的上下文长度上限,适配长文本 / 多模态输入
学习率调节器cosine(余弦退火)训练后期平滑降低学习率,帮助模型稳定收敛
计算类型bf16半精度计算(需显卡支持 Ampere 及以上架构),进一步节省显存并加速训练

微调完整后,得到lora权重,默认保存在LLaMA Factory目录下的./saves/Qwen3-VL-4B-Instruct/lora/xxxxxx

3.2、合并权重

这里的思路:基础Qwen3-VL-4b权重 + lora权重 = 完整微调权重

我们只需确定:基础权重的路径model_name_or_path、lora微调权重路径adapter_name_or_path

然后执行下面命令,进行合并:

llamafactory-cli export \--model_name_or_path "/home/user/.cache/huggingface/hub/models--Qwen--Qwen3-VL-4B-Instruct/snapshots/ebb281ec70b05090aa6165b016eac8ec08e71b17/" \--adapter_name_or_path ./saves/Qwen3-VL-4B-Instruct/lora/train_2025-11-11-08-59-06/ \--export_dir ./model_path/qwen3-vl-4b-merged-t \--template qwen3_vl_nothink

等待合并完成,合并后的权重大小:

8.3G    model_path/qwen3-vl-4b-merged

3.3、使用bnb进行4bit量化 

通过BitsAndBytes工具Qwen3-VL 模型进行 4 位量化,以降低模型显存占用,

同时保存量化后的模型、分词器(tokenizer)和处理器(processor),便于后续部署使用。

  • 使用transformers库中的模型加载类(Qwen3VLForConditionalGenerationAutoModel)、量化配置(BitsAndBytesConfig
  • 依赖torch进行张量计算和设备管理。

模型量化的实力代码,如下所示:

from transformers import Qwen3VLForConditionalGeneration, AutoModel, BitsAndBytesConfig, AutoTokenizer, AutoProcessor
import torchdef quantize_model():# 配置4位bnb量化quantization_config = BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_compute_dtype=torch.float16,bnb_4bit_quant_type="nf4",bnb_4bit_use_double_quant=True,)try:# 尝试使用特定模型类print("尝试使用 Qwen3VLForConditionalGeneration 加载模型...")model = Qwen3VLForConditionalGeneration.from_pretrained("./model_path/qwen3-vl-4b-merged",quantization_config=quantization_config,device_map="auto",trust_remote_code=True)except Exception as e:print(f"使用特定类失败: {e}")print("尝试使用 AutoModel 加载模型...")# 回退到 AutoModelmodel = AutoModel.from_pretrained("./model_path/qwen3-vl-4b-merged",quantization_config=quantization_config,device_map="auto",trust_remote_code=True)# 加载tokenizer和processortokenizer = AutoTokenizer.from_pretrained("./model_path/qwen3-vl-4b-merged",trust_remote_code=True)processor = AutoProcessor.from_pretrained("./model_path/qwen3-vl-4b-merged",trust_remote_code=True)# 保存量化后的模型和相关组件model.save_pretrained("./model_path/qwen3-vl-4b-bnb-quantized-merged")tokenizer.save_pretrained("./model_path/qwen3-vl-4b-bnb-quantized-merged")processor.save_pretrained("./model_path/qwen3-vl-4b-bnb-quantized-merged")print("模型量化并保存完成!")if __name__ == "__main__":quantize_model()

通过BitsAndBytesConfig定义量化参数,核心配置包括:

  • load_in_4bit=True:启用 4 位量化(相比 16 位 / 32 位大幅减少显存占用)。
  • bnb_4bit_compute_dtype=torch.float16:计算时使用 float16 精度,平衡效率与性能。
  • bnb_4bit_quant_type="nf4":采用 “Normalized Float 4” 量化类型,专为自然语言模型优化,精度优于普通 4 位量化。
  • bnb_4bit_use_double_quant=True:启用双重量化(对量化参数再量化),进一步减少内存开销。

量化后的权重大小:

2.7G    model_path/qwen3-vl-4b-bnb-quantized-merged

3.4、量化后的模型推理(实践案例)

这个模型推理示例,是 Qwen3-VL 量化模型的特定类别物体检测与可视化

核心用于批量处理图像提取目标物体多维度信息生成可视化结果

核心功能:

  • 支持指定任意类别的物体(table、cup、bottle 等),输出类别、边界框、颜色、形状、外观 5 类信息。
  • 支持批量读取图像目录,自动按自然排序处理文件。
  • 生成带边界框和类别的可视化图像,同时打印详细物体描述。
  • 包含异常处理和格式容错,确保流程稳定性。

推理代码:

import os
import json
import glob
import re
import torch
import numpy as np
from PIL import Image
import cv2
import time
from transformers import AutoProcessor, AutoModelForImageTextToText# --- 基础路径 ---
QUANTIZED_MODEL_PATH = "./model_path/qwen3-vl-4b-bnb-quantized-merged" # 融合lora微调 4-bit量化
input_dir = "./dataset_label/01_office/"
output_dir = "./Output_DATA/01_office"
# 定义要检测的物体类别
NODE_SPACE = ['table', 'cup', 'bottle', 'chair', 'robot', 'garbage can', 'shelf', 'tissue box', 'potted plant']# 全局初始化模型和处理器
print(f"正在加载修复后的量化Qwen3-VL-4B模型:{QUANTIZED_MODEL_PATH}...")
try:# 使用修复后的量化模型(不指定dtype)model = AutoModelForImageTextToText.from_pretrained(QUANTIZED_MODEL_PATH,device_map="auto",trust_remote_code=True)processor = AutoProcessor.from_pretrained(QUANTIZED_MODEL_PATH)print("修复后的量化Qwen3-VL-4B模型和处理器加载成功!")except Exception as e:print(f"模型加载失败:{str(e)}")exit(1)def call_vlm(image_path, prompt):"""安全的VLM调用函数"""try:# >>>>> 修改点1:直接使用 PIL 加载图像 <<<<<# 假设模型能处理任意大小的有效图像,不再强制调整尺寸image = Image.open(image_path).convert("RGB") # 确保是 RGB 模式messages = [{"role": "user","content": [{"type": "image"},{"type": "text", "text": prompt}]}]text = processor.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)inputs = processor(text=text,images=image,return_tensors="pt",padding=True).to(model.device)# 生成参数with torch.no_grad():generated_ids = model.generate(**inputs,max_new_tokens=512,do_sample=False,num_beams=1,early_stopping=False)generated_ids_trimmed = generated_ids[:, inputs.input_ids.shape[1]:]output_text = processor.batch_decode(generated_ids_trimmed,skip_special_tokens=True,clean_up_tokenization_spaces=False)[0]return output_textexcept Exception as e:print(f"VLM调用失败: {str(e)}")return ""def detect_and_describe_objects(image_path):"""检测图像中的物体,获取其描述并反归一化bbox"""# 构建提示词prompt = f"""请严格检测图像中属于以下类别的所有物体:{NODE_SPACE}。对于每个检测到的物体,请提供:1. 类别 (category)2. 紧贴边缘的边界框 (bbox),格式为 [x1,y1,x2,y2](整数像素坐标)3. 颜色 (color)4. 形状 (shape)5. 外观描述 (appearance)输出要求:- 仅识别列表中的类别。- bbox必须精确且紧密贴合物体边缘。- 描述信息需与bbox内的区域一致。- 严格以 JSON 数组形式返回,不要包含任何额外的文本或代码块标记。输出示例(格式参考):[{{"category": "cup","bbox": [97, 203, 176, 282],"color": "白色","shape": "圆柱形","appearance": "带把手陶瓷杯"}},{{"category": "table","bbox": [10, 318, 639, 474],"color": "原木色","shape": "长方形","appearance": "四腿木质桌面"}}]"""try:# >>>>> 修改点2:直接使用 PIL 获取图像尺寸 <<<<<# 加载图像以获取尺寸用于坐标反归一化with Image.open(image_path) as img_pil:w_img, h_img = img_pil.convert("RGB").size # 确保获取到尺寸print("开始调用VLM进行物体检测...")time_start = time.time()response = call_vlm(image_path, prompt)time_end = time.time()print(f'VLM推理时间:{time_end - time_start:.2f}s')if not response:print("VLM返回空响应")return []# 解析JSON响应try:objects_data = json.loads(response.strip())except json.JSONDecodeError:# 尝试清理常见的格式问题cleaned_response = response.strip()if cleaned_response.startswith('```json'):cleaned_response = cleaned_response[7:]if cleaned_response.startswith('```'):cleaned_response = cleaned_response[3:]if cleaned_response.endswith('```'):cleaned_response = cleaned_response[:-3]cleaned_response = cleaned_response.strip()try:objects_data = json.loads(cleaned_response)except json.JSONDecodeError as e:print(f"JSON解析失败: {e}")print(f"原始响应内容: {response}")return []if not isinstance(objects_data, list):print(f"响应不是列表格式: {objects_data}")return []# 处理并验证每个检测到的物体valid_objects = []for i, obj in enumerate(objects_data):# 验证基本结构if not (isinstance(obj, dict) and obj.get('category') in NODE_SPACE and len(obj.get('bbox', [])) == 4):print(f"警告:跳过无效的检测结果 #{i+1}: {obj}")continue# 提取并转换bbox坐标try:coords = list(map(float, obj['bbox']))x1_norm, y1_norm, x2_norm, y2_norm = coordsexcept (ValueError, TypeError) as e:print(f"警告:跳过坐标格式错误的物体 #{i+1}: {e}")continue# 反归一化 (假设模型输出的是0-1000范围的坐标)x1 = int(round(x1_norm / 1000 * w_img))y1 = int(round(y1_norm / 1000 * h_img))x2 = int(round(x2_norm / 1000 * w_img))y2 = int(round(y2_norm / 1000 * h_img))# 确保坐标在图像范围内x1 = max(0, min(x1, w_img - 1))y1 = max(0, min(y1, h_img - 1))x2 = max(x1 + 1, min(x2, w_img - 1))y2 = max(y1 + 1, min(y2, h_img - 1))# 更新对象字典obj['bbox'] = [x1, y1, x2, y2]valid_objects.append(obj)print(f"共检测并验证了 {len(valid_objects)} 个物体。")return valid_objectsexcept Exception as e:print(f"物体检测过程失败: {str(e)}")return []def visualize_detections(image_path, detected_objects, output_path):"""可视化检测结果:绘制bbox和标签"""try:# 读取图像image_bgr = cv2.imread(image_path)if image_bgr is None:print(f"无法读取图像: {image_path}")returnh_img, w_img = image_bgr.shape[:2] # 获取OpenCV图像的尺寸# 绘制每个检测到的物体for i, obj in enumerate(detected_objects):bbox = obj.get('bbox', [0, 0, 0, 0])category = obj.get('category', 'Unknown')# 确保坐标在有效范围内 (基于OpenCV图像尺寸)x1, y1, x2, y2 = bboxx1 = max(0, min(x1, w_img - 1))y1 = max(0, min(y1, h_img - 1))x2 = max(x1 + 1, min(x2, w_img - 1))y2 = max(y1 + 1, min(y2, h_img - 1))# 绘制边界框cv2.rectangle(image_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2)# 准备标签文本label_parts = [category]# 可以选择性地添加颜色、形状等信息到标签# color = obj.get('color', '')# shape = obj.get('shape', '')# if color: label_parts.append(color)# if shape: label_parts.append(shape)label = " ".join(label_parts)# 计算标签位置(在框上方)(text_width, text_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)y_label = y1 - 10 if y1 - 10 > 10 else y1 + 10 + text_height# 绘制标签背景矩形cv2.rectangle(image_bgr, (x1, y_label - text_height - baseline), (x1 + text_width, y_label + baseline), (0, 255, 0), -1)# 绘制标签文字cv2.putText(image_bgr, label, (x1, y_label),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)# 保存可视化图像cv2.imwrite(output_path, image_bgr)print(f"检测结果可视化已保存至: {output_path}")except Exception as e:print(f"可视化检测结果失败: {str(e)}")def print_object_descriptions(detected_objects):"""打印每个检测到的物体的详细描述信息"""if not detected_objects:print("未检测到任何物体。")returnprint("\n--- 检测到的物体描述 ---")for i, obj in enumerate(detected_objects):print(f"物体 #{i+1}:")print(f"  类别 (Category): {obj.get('category', 'N/A')}")print(f"  边界框 (Bbox): {obj.get('bbox', 'N/A')}")print(f"  颜色 (Color): {obj.get('color', 'N/A')}")print(f"  形状 (Shape): {obj.get('shape', 'N/A')}")print(f"  外观 (Appearance): {obj.get('appearance', 'N/A')}")print("-" * 20)print("--- 描述结束 ---\n")def natural_sort_key(filename):"""自然排序含数字的文件名"""return [int(t) if t.isdigit() else t.lower()for t in re.split(r'(\d+)', os.path.basename(filename))]def process_images_simple(input_dir, output_dir):"""批量处理图像目录,执行简化任务"""try:os.makedirs(output_dir, exist_ok=True)viz_output_dir = os.path.join(output_dir, "visualizations_simple")os.makedirs(viz_output_dir, exist_ok=True)image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.gif']image_files = []for ext in image_extensions:image_files.extend(glob.glob(os.path.join(input_dir, ext)))image_files = sorted(image_files, key=natural_sort_key)if not image_files:print(f"在目录 {input_dir} 中未找到图像文件")returnfor image_path in image_files:image_name = os.path.basename(image_path)print(f"\n---------- 处理图像: {image_name} ----------")# 1. 检测物体并获取描述detected_objects = detect_and_describe_objects(image_path)# 2. 打印物体描述print_object_descriptions(detected_objects)# 3. 可视化检测框viz_filename = os.path.splitext(image_name)[0] + "_simple_viz.jpg"viz_path = os.path.join(viz_output_dir, viz_filename)visualize_detections(image_path, detected_objects, viz_path)print(f"---------- 完成处理: {image_name} ----------\n")print("\n所有图像处理完成")except Exception as e:print(f"处理图像目录失败: {str(e)}")if __name__ == "__main__":import argparseparser = argparse.ArgumentParser(description='简化版Qwen3-VL物体检测与可视化工具')parser.add_argument('--input_dir', type=str, default=input_dir, help='包含图像的输入目录')parser.add_argument('--output_dir', type=str, default=output_dir, help='输出结果的目录')args = parser.parse_args()process_images_simple(args.input_dir, args.output_dir)

代码流程:

  1. 初始化:加载量化模型和处理器,自动适配设备(CPU/GPU)。
  2. 图像读取:批量获取指定目录下的多种格式图像,按自然排序处理。
  3. VLM 调用:通过提示词引导模型检测目标物体,返回 JSON 格式结果。
  4. 结果处理:解析 JSON 响应(含格式容错),将归一化坐标反转为像素坐标并校验范围。
  5. 可视化与输出:用 OpenCV 绘制边界框和标签,保存图像并打印详细物体信息。

运行效果:

打印信息:

---------- 处理图像: 011002.jpg ----------
开始调用VLM进行物体检测...
VLM推理时间:26.43s
共检测并验证了 7 个物体。

--- 检测到的物体描述 ---
物体 #1:
  类别 (Category): table
  边界框 (Bbox): [0, 48, 639, 479]
  颜色 (Color): 原木色
  形状 (Shape): 圆形
  外观 (Appearance): 木质圆桌,表面有食物和餐具
--------------------
物体 #2:
  类别 (Category): cup
  边界框 (Bbox): [396, 74, 458, 157]
  颜色 (Color): 透明
  形状 (Shape): 圆柱形
  外观 (Appearance): 玻璃杯,装有橙色液体
--------------------
物体 #3:
  类别 (Category): cup
  边界框 (Bbox): [190, 281, 272, 385]
  颜色 (Color): 透明
  形状 (Shape): 圆柱形
  外观 (Appearance): 玻璃杯,装有清水
--------------------
物体 #4:
  类别 (Category): bottle
  边界框 (Bbox): [339, 4, 403, 205]
  颜色 (Color): 绿色
  形状 (Shape): 圆柱形
  外观 (Appearance): 玻璃瓶,瓶身有蓝色标签和文字
--------------------
物体 #5:
  类别 (Category): bottle
  边界框 (Bbox): [224, 0, 304, 143]
  颜色 (Color): 黄色
  形状 (Shape): 圆柱形
  外观 (Appearance): 玻璃瓶,瓶身有绿色标签和文字
--------------------
物体 #6:
  类别 (Category): bottle
  边界框 (Bbox): [240, 104, 298, 297]
  颜色 (Color): 银色
  形状 (Shape): 圆柱形
  外观 (Appearance): 金属瓶,瓶身有黑色盖子
--------------------
物体 #7:
  类别 (Category): chair
  边界框 (Bbox): [554, 0, 639, 186]
  颜色 (Color): 白色
  形状 (Shape): 四脚椅
  外观 (Appearance): 白色塑料椅,部分可见
--------------------
--- 描述结束 ---

检测结果可视化已保存至: ./Output_DATA/COCO-trian-2017-Graph/visualizations_simple/011002_simple_viz.jpg
---------- 完成处理: 011002.jpg ----------

其他运行示例:

NVIDIA Orin的资源占用情况:

分享完成~

http://www.dtcms.com/a/618661.html

相关文章:

  • 网站建设状况vue快速搭建网站
  • 深入数据库性能优化:从参数调优到RAC高可用架构构建
  • MaxTex下载及LaTex环境配置
  • HttpServletResponse 详细指南
  • 网站建设3a模型是什么意思即墨网站建设哪家好
  • 为什么网站设计很少全屏网络维护难吗
  • 北京做网站的公司商集客电话专业app开发设计的公司
  • SpringCache缓存
  • kubernetes基于sealos工具快速安装指导
  • Linux 信号机制
  • SpringBoot19-HttpClient 详解及 SpringBoot 使用指南
  • 17做网店一样的网站网站按域名跳转不同的页面
  • 13.2 国产之光崛起:深度求索与通义千问的技术突破
  • 旅游网站建设的结论阿里云商标注册官网
  • 第五次:郑州银行杯2025郑州马拉松
  • Three.js使用教程
  • Reqable 工具报错 Netbare Code Error Unknown
  • 宝山网页设计制作黄石seo诊断
  • git-Git约定式提交
  • wap建站教程0元玩手游平台
  • nw.js桌面软件开发系列 第.节 HTML和桌面软件开发的碰撞
  • 设计一套网站费用北京网页
  • 7.3、Python-函数的返回值
  • 网站建设咨询话术技巧网站开发程序设计
  • 【Qt】配置安卓开发环境
  • 基于Qt,调用千问7B大模型,实现智能对话
  • Ubuntu 美化
  • 网站互动营销专门app软件开发公司
  • .net开发微信网站流程网站怎么做收费
  • 变分自编码器(VAE)的原理方法(一)