✅ 实现目标

  1. 按下空格键 → 获取摄像头当前画面;

  2. 将图片上传给 大模型 接口,让其“看图说话”;

  3. 获取返回描述后,以字幕形式展示在图像画面上;

  4. 持续显示识别结果,直到下次按空格。

🧠 需要准备的

  • ✅ Python 环境(3.8+)

  • ✅ OpenCV (cv2)

  • ✅ 阿里千问 API 的 Key

  • ✅ 安装以下依赖:

pip install opencv-python requests pillow

需要同时保留 YOLOv8 的实时目标检测,并在按下空格时截屏上传给 大模型 接口做“看图说话”,然后将返回的中文描述以字幕形式叠加在画面中。

✅ 总体功能整合:

功能项描述
✅ YOLOv8 实时检测保留,检测物体并标注
✅ 空格触发 千问 描述按下空格键拍照并上传
✅ 显示中文字幕将 DeepSeek 返回文字叠加到图像上
✅ 中文字体支持使用 PIL 实现中文绘图支持

import cv2
import numpy as np
from ultralytics import YOLO
from PIL import ImageFont, ImageDraw, Image
import requests
from io import BytesIO
import os
from openai import OpenAI
import base64
from typing import Optional, Union
import logging
import json
import time
from collections import deque
import threading# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# 文字
current_caption = ""
# 存储字幕:(text, timestamp)
captions = deque()def img2text(image_path: str, prompt: str = "这张图片是什么,一句话来描述") -> Optional[Union[str, dict]]:global current_caption  # 关键:修改全局变量"""将图像转换为文本描述Args:image_path (str): 图像文件路径prompt (str): 提示词,默认为"这张图片是什么"Returns:Optional[Union[str, dict]]: API响应结果,失败时返回None"""# base64_image = image_to_base64(image_path)base64_image = image_pathif not base64_image:return Noneclient = OpenAI(api_key="sk-02128251fc324da9800c2553d67fa2ca",base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",)try:completion = client.chat.completions.create(model="qwen-vl-plus-2025-01-25",messages=[{"role": "user","content": [{"type": "text", "text": prompt},{"type": "image_url","image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}]}],# stream=False,stream=True,stream_options={"include_usage": False})# print(completion.model_dump_json())full_text = ""# for chunk in completion:#     result = json.loads(chunk.model_dump_json())#     text = result["choices"][0]["delta"]["content"]#     current_caption = text#     print("响应:",current_caption)current_line = ""for chunk in completion:result = json.loads(chunk.model_dump_json())word = result["choices"][0]["delta"]["content"]current_line += wordprint("响应:", word)if "。" in word or "?" in word or "!" in word:timestamp = time.time()captions.append((current_line.strip(), timestamp))current_line = ""if current_line.strip():  # 最后一段未结束的也保留captions.append((current_line.strip(), time.time()))except Exception as e:logger.error(f"API调用错误: {e}")return None# ========== 中文字体 ==========
font_path = "font/AlimamaDaoLiTi-Regular.ttf"  # MacOS 示例
font = ImageFont.truetype(font_path, 28)# ========== 加载 YOLO 模型 ==========
model = YOLO("/Users/lianying/Desktop/yolo/yolov8n.pt")  # 可换为 yolov8n.pt/yolov8s.pt 等# ========== 摄像头 ==========
cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()if not ret:break# YOLOv8 实时检测results = model(frame, verbose=False)[0]annotated_frame = results.plot()  # 获取带标注的帧# 叠加 DeepSeek 返回的字幕(如果有)frame_pil = Image.fromarray(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(frame_pil)# 绘制字幕(只显示近3秒的)now = time.time()frame_pil = Image.fromarray(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(frame_pil)y_offset = 30for caption, ts in list(captions):if now - ts <= 3:draw.rectangle([20, y_offset - 10, 1200, y_offset + 35], fill=(0, 0, 0, 128))draw.text((30, y_offset), caption, font=font, fill="yellow")print(caption)y_offset += 50else:captions.popleft()  # 删除过期字幕frame_final = cv2.cvtColor(np.array(frame_pil), cv2.COLOR_RGB2BGR)cv2.imshow("YOLOv8 实时检测 + 图像描述(按空格)", frame_final)key = cv2.waitKey(1)if key == 27:  # ESC 退出breakelif key == 32:  # 空格触发print("正在发送当前帧给 DeepSeek,请稍等...")_, buffer = cv2.imencode(".jpg", frame)image_bytes = BytesIO(buffer.tobytes())base64_str = base64.b64encode(buffer).decode('utf-8')print("Base64 (PNG):", base64_str[:50] + "...")img2text(base64_str)cap.release()
cv2.destroyAllWindows()

✅ 按下空格的时候为什么卡顿?

你这段代码里,空格按下时调用的是这个同步过程:img2text(base64_str)

这一段是网络请求 + AI处理 + 流式输出 + 截图处理,在本地和远程服务器之间来回传输数据,因此:

  • YOLO 视频帧不再刷新

  • cv2.imshow() 阻塞;

  • 显示“卡住”不动,直到接口响应。

✅ 如何解决卡顿问题?

 ✅ 使用 线程 异步处理图片描述,避免主线程阻塞:

在你主程序中添加多线程工具包:

import threading

将触发图像描述的代码改成:

elif key == 32:  # 空格触发print("正在发送当前帧给 DeepSeek,请稍等...")# 异步处理图像上传与字幕生成def async_caption():_, buffer = cv2.imencode(".jpg", frame)base64_str = base64.b64encode(buffer).decode('utf-8')img2text(base64_str)  # 你之前写的函数threading.Thread(target=async_caption, daemon=True).start()

✅ 整体流程优化后效果

  • 主线程负责视频帧采集 + YOLO 检测 + 字幕显示

  • 图像描述调用单独在线程中执行,后台上传+接收,不影响主线程

  • 效果:按下空格后,摄像头不会卡顿;字幕几秒后自动出现

✅ 完整代码

import cv2
import numpy as np
from ultralytics import YOLO
from PIL import ImageFont, ImageDraw, Image
import requests
from io import BytesIO
import os
from openai import OpenAI
import base64
from typing import Optional, Union
import logging
import json
import time
from collections import deque
import threading# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# 文字
current_caption = ""
# 存储字幕:(text, timestamp)
captions = deque()def img2text(image_path: str, prompt: str = "这张图片是什么,一句话来描述") -> Optional[Union[str, dict]]:global current_caption  # 关键:修改全局变量"""将图像转换为文本描述Args:image_path (str): 图像文件路径prompt (str): 提示词,默认为"这张图片是什么"Returns:Optional[Union[str, dict]]: API响应结果,失败时返回None"""# base64_image = image_to_base64(image_path)base64_image = image_pathif not base64_image:return Noneclient = OpenAI(api_key="sk-02128251fc324da9800c2553d67fa2ca",base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",)try:completion = client.chat.completions.create(model="qwen-vl-plus-2025-01-25",messages=[{"role": "user","content": [{"type": "text", "text": prompt},{"type": "image_url","image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}]}],# stream=False,stream=True,stream_options={"include_usage": False})# print(completion.model_dump_json())full_text = ""# for chunk in completion:#     result = json.loads(chunk.model_dump_json())#     text = result["choices"][0]["delta"]["content"]#     current_caption = text#     print("响应:",current_caption)current_line = ""for chunk in completion:result = json.loads(chunk.model_dump_json())word = result["choices"][0]["delta"]["content"]current_line += wordprint("响应:", word)if "。" in word or "?" in word or "!" in word:timestamp = time.time()captions.append((current_line.strip(), timestamp))current_line = ""if current_line.strip():  # 最后一段未结束的也保留captions.append((current_line.strip(), time.time()))except Exception as e:logger.error(f"API调用错误: {e}")return None# ========== 中文字体 ==========
font_path = "font/AlimamaDaoLiTi-Regular.ttf"  # MacOS 示例
font = ImageFont.truetype(font_path, 28)# ========== 加载 YOLO 模型 ==========
model = YOLO("/Users/lianying/Desktop/yolo/yolov8n.pt")  # 可换为 yolov8n.pt/yolov8s.pt 等# ========== 摄像头 ==========
cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()if not ret:break# YOLOv8 实时检测results = model(frame, verbose=False)[0]annotated_frame = results.plot()  # 获取带标注的帧# 叠加 DeepSeek 返回的字幕(如果有)frame_pil = Image.fromarray(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(frame_pil)# 绘制字幕(只显示近3秒的)now = time.time()frame_pil = Image.fromarray(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(frame_pil)y_offset = 30for caption, ts in list(captions):if now - ts <= 3:draw.rectangle([20, y_offset - 10, 1200, y_offset + 35], fill=(0, 0, 0, 128))draw.text((30, y_offset), caption, font=font, fill="yellow")print(caption)y_offset += 50else:captions.popleft()  # 删除过期字幕# if current_caption:#     print(current_caption)# draw.text((30, 30), current_caption, font=font, fill="yellow")frame_final = cv2.cvtColor(np.array(frame_pil), cv2.COLOR_RGB2BGR)cv2.imshow("YOLOv8 实时检测 + 图像描述(按空格)", frame_final)key = cv2.waitKey(1)if key == 27:  # ESC 退出breakelif key == 32:  # 空格触发print("正在发送当前帧给 DeepSeek,请稍等...")# 异步处理图像上传与字幕生成def async_caption():_, buffer = cv2.imencode(".jpg", frame)base64_str = base64.b64encode(buffer).decode('utf-8')img2text(base64_str)  # 你之前写的函数threading.Thread(target=async_caption, daemon=True).start()# elif key == 32:  # 空格触发#     print("正在发送当前帧给 DeepSeek,请稍等...")#     _, buffer = cv2.imencode(".jpg", frame)#     image_bytes = BytesIO(buffer.tobytes())#     base64_str = base64.b64encode(buffer).decode('utf-8')#     print("Base64 (PNG):", base64_str[:50] + "...")#     img2text(base64_str)cap.release()
cv2.destroyAllWindows()

 ✅ 效果图:

展示部分截图,其他不方便展示

视频:

yolo8实现小艺看世界

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916963.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916963.shtml
英文地址,请注明出处:http://en.pswp.cn/news/916963.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ee类保研面试】数学类---线性代数

25保研er&#xff0c;希望将自己的面试复习分享出来&#xff0c;供大家参考 part0—英语类 part1—通信类 part2—信号类 part3—高数类 part100—self项目准备 文章目录线性代数知识点大全**1. 余子式与代数余子式****2. 行列式的含义****3. 矩阵的秩&#xff08;Rank&#xf…

在 Scintilla 中为 Squirrel 语言设置语法解析器的方法

Scintilla 作为一个强大的开源文本编辑控件&#xff0c;通过配置语法解析器&#xff0c;能够对多种编程语言实现语法高亮、代码折叠等实用功能。若要为新语言 Squirrel 设置语法解析器&#xff0c;可参考以下步骤&#xff1a;​创建 Lexer 源文件&#xff1a;Scintilla 通过 Le…

Go语言核心知识点补充

Go语言核心知识点补充 make函数、for循环与输入处理详解 在前几章的内容中&#xff0c;我们介绍了Go语言的基础语法、变量声明、切片、循环等核心概念。但在实际开发中&#xff0c;一些细节性的知识点往往决定了代码的健壮性与效率。 本文将针对前几章涉及到的变量声明与初始化…

AI服务器中,EEPROM有哪些部件使用,需要存储哪些信息?

在AI服务器中&#xff0c;EEPROM&#xff08;电可擦可编程只读存储器&#xff09;主要用于存储关键组件的配置数据、身份信息和校准参数。以下是主要组件及其存储内容&#xff1a; 一、核心组件及存储数据主板&#xff08;Baseboard Management Controller, BMC&#xff09; FR…

It学习资源下载

一.UI 8个高质量UI设计网站&#xff0c;灵感收集必备&#xff01;

Docker Compose :从入门到企业级部署

Docker Compose &#xff1a;从入门到企业级部署1. Docker Compose 核心概念1.1 Compose 架构全景图2. 完整开发工作流2.1 典型开发流程2.2 多服务示例项目结构3. 核心配置详解3.1 服务配置矩阵3.2 网络拓扑示例4. 企业级部署方案4.1 多环境配置管理4.2 扩展部署架构5. 高级技巧…

1.2.vue插值表达式

在 Vue.js 中&#xff0c;插值表达式是用于在模板中显示数据的一种方式。它使用双大括号语法 {{ }} 来包裹需要输出的变量或表达式的值。Vue 会自动将这些表达式的值插入到 HTML 文档中相应的位置。插值表达式基本用法最基本的插值表达式形式就是直接在模板中引用 Vue 实例中的…

Python数据处理基础(学习笔记分享)

Python数据处理入门 常用库学习 numpy NumPy&#xff08;Numerical Python&#xff09; 是 Python 中用于高效数值计算的库&#xff0c;核心是提供一个强大的 ndarray​&#xff08;多维数组&#xff09;对象&#xff0c;类似于 C/C 中的数组&#xff0c;但支持更丰富的操作&a…

力扣面试150题--颠倒二进制位

Day 89 题目描述思路 二进制的算法&#xff0c;将十进制转化为二进制&#xff0c;有一点需要注意&#xff0c;直接采取库函数转化为二进制再反转会出现问题&#xff08;这也是为什么我要补0的原因&#xff09;&#xff0c;因为转化过去不满足32位的二进制&#xff0c;前面不会当…

【ResNet50图像分类部署至RK3588】模型训练→转换RKNN→开发板部署

已在GitHub开源与本博客同步的ResNet50v2_RK3588_Classificationt项目&#xff0c;地址&#xff1a;https://github.com/A7bert777/ResNet50v2_RK3588_Classification 详细使用教程&#xff0c;可参考README.md或参考本博客第八章 模型部署 文章目录一、项目回顾二、模型选择介…

C# _泛型

目录 泛型是什么? 泛型的主要优势 创建一个泛型类 泛型方法 泛型是什么? 泛型是通过参数化来实现同一份代码上操作多种数据类型 利用参数类型将参数的类型抽象化 从而实现灵活的复用 总结: 通过泛型可以实现在同一份代码上操作多种数据类型的逻辑 将类和类中的成员定义…

Vue路由钩子完全指南

Vue.js中的路由导航钩子&#xff08;Navigation Guards&#xff09;主要用于在路由导航过程中进行拦截和处理&#xff0c;确保访问控制和状态管理。以下是主要分类及使用方法&#xff1a; 1. 全局钩子函数 作用于整个路由实例&#xff0c;需在路由配置外定义&#xff1a; befor…

RAGFlow 登录界面点击登录无反应,控制台报错 502 Bad Gateway 解决方法

遇到的问题 在使用RAGFlow的时候&#xff0c;登录不进去&#xff0c;但是之前能登录。 还出现了输入地址直接进入工作界面&#xff0c;但是进行不了任何操作的bug&#xff1b;以及无法上传文档的问题&#xff08;其实都是因为没登录&#xff09;。 登陆界面报错如图显示。 …

数据结构第3问:什么是线性表?

线性表 线性表由具有相同数据类型的n个元素构成&#xff0c;这些元素之间存在一一对应的线性关系。其中n为表长&#xff0c;当n0的时候线性表是一个空表。简单来说&#xff0c;线性表中的元素排列成一条线&#xff0c;每个元素最多有一个直接的前驱和后继&#xff08;除第一个和…

常见CMS 靶场复现

一、wordpass1.修改模版文件getshell搭建网站登录网站后台更改网站模版的相关文件写入一句话木马凭借路径访问/wp-content/themes/twentyfifteen/404.php/?aphpinfo();2.上传夹带木马的主题getshell外观-->主题-->添加-->上传-->浏览-->安装-->访问木马文件…

Elasticsearch - 倒排索引原理和简易实现

倒排索引的功能设计倒排索引&#xff08;Inverted Index&#xff09;是一种高效的数据结构&#xff0c;常用于全文搜索和信息检索系统。它的核心思想是将文档中每个关键字&#xff08;term&#xff09;与包含该关键字的文档列表进行映射。以下是实现倒排索引功能的设计步骤和代…

C#开发的Panel里控件拖放例子 - 开源研究系列文章

上次写了Panel的分页滚动控件( C#开发的Panel滚动分页控件&#xff08;滑动版&#xff09; - 开源研究系列文章 - Lzhdims Fashion - 博客园 )&#xff0c;但是主要是想写一个Panel里控件拖放的效果&#xff0c;然后分页控件用于Panel里控件的分页。此文这次写的是控件拖放效果…

Thinkph6中常用的验证方式实例

我们在使用thinkphp6中的数据验证时&#xff0c;如果使用不多的话&#xff0c;会经常遇到校验不对&#xff0c;在这个小问题上折腾很多&#xff0c;索引就不用了。我还不如直接写if条件来的迅捷&#xff01;&#xff01;下面把常见的校验方法进行一下整理&#xff1a;protected…

分享一个FPGA寄存器接口自动化工具

FPGA模块越写越多&#xff0c;规范性和可移植性却堪忧。要是有一个工具可以根据模块接口描述文件生成verilog和c头文件就好了。苦苦搜寻找到了几款免费的工具&#xff0c;SystemRDL、cheby和rggen。笔者学习了下cheby和reksio&#xff0c;reksio是gui版的cheby&#xff0c;这是…

小程序中事件对象的属性与方法

在小程序中&#xff0c;事件处理函数的参数为事件对象&#xff08;通常命名为 e&#xff09;&#xff0c;包含了事件相关的详细信息&#xff08;如事件类型、触发元素、传递的数据等&#xff09;。事件对象的属性和方法因事件类型&#xff08;如点击、输入、触摸等&#xff09;…