forked from zbw/yiliao2026
Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit. On branch master Your branch is ahead of 'origin/master' by 2 commits. (use "git push" to publish your local commits) Changes to be committed: modified: .gitignore modified: README.md new file: bashes/auto-wifi-connect.service new file: bashes/auto-wifi-connect.sh deleted: keyboard_control.py new file: my_model/image.png new file: path_follower_demo.py new file: scripts/PIDtracking.py new file: scripts/__pycache__/publish_sine_path.cpython-310.pyc new file: scripts/publish_sine_path.py modified: src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml modified: src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml.bak new file: src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml.bak2 modified: src/origincar_base/config/ekf.yaml new file: src/origincar_base/config/ekf.yaml.bak modified: src/origincar_base/launch/base_serial.launch.py new file: src/origincar_base/launch/base_serial.launch.py.bak modified: src/origincar_base/launch/origincar_bringup.launch.py new file: src/past_control/CMakeLists.txt new file: src/past_control/config/past_control.yaml new file: src/past_control/include/past_control/tools.h new file: src/past_control/launch/past_control.launch.py new file: src/past_control/msg/Obstacle.msg new file: src/past_control/msg/ObstacleArray.msg new file: src/past_control/package.xml new file: src/past_control/src/lane_follower_node.cpp new file: src/past_control/src/obstacle_detector_node.cpp new file: src/past_control/src/racing_orchestrator.cpp new file: src/planner/CMakeLists.txt new file: src/planner/config/planner.yaml new file: src/planner/launch/planner.launch.py new file: src/planner/package.xml new file: src/planner/src/planner_version.cpp modified: src/qr_detection/src/qr_dete_depth.cpp new file: src/racing_control/CMakeLists.txt new file: src/racing_control/include/racing_control/racing_control.hpp new file: src/racing_control/package.xml new file: src/racing_control/src/racing_control.cpp modified: src/vlm_detect/setup.py new file: src/vlm_detect/vlm_detect/__pycache__/__init__.cpython-310.pyc new file: src/vlm_detect/vlm_detect/__pycache__/tts_node.cpython-310.pyc new file: src/vlm_detect/vlm_detect/test_publisher.py new file: src/vlm_detect/vlm_detect/tts_node.py modified: src/vlm_detect/vlm_detect/vlm_node.py new file: tools/measure_turning_radius.py new file: tools/set_volume.py new file: tools/udp_to_cmdvel.py new file: tools/windows_keyboard_control.py new file: vlm_server.py new file: "\350\260\203\350\257\225\350\256\260\345\275\225.Assets/1.png" renamed: "\350\260\203\350\257\225\350\256\260\345\275\225.log" -> "\350\260\203\350\257\225\350\256\260\345\275\225.md"
This commit is contained in:
@@ -21,6 +21,8 @@ setup(
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'vlm_node = vlm_detect.vlm_node:main',
|
||||
'test_publisher = vlm_detect.test_publisher:main',
|
||||
'tts_node = vlm_detect.tts_node:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
BIN
src/vlm_detect/vlm_detect/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
src/vlm_detect/vlm_detect/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
src/vlm_detect/vlm_detect/__pycache__/tts_node.cpython-310.pyc
Normal file
BIN
src/vlm_detect/vlm_detect/__pycache__/tts_node.cpython-310.pyc
Normal file
Binary file not shown.
88
src/vlm_detect/vlm_detect/test_publisher.py
Normal file
88
src/vlm_detect/vlm_detect/test_publisher.py
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试发布者:发送图片和触发信号给 VLM 节点
|
||||
用法: ros2 run vlm_detect test_publisher --ros-args -p image_path:="/path/to/image.jpg"
|
||||
"""
|
||||
|
||||
import rclpy
|
||||
from rclpy.node import Node
|
||||
from std_msgs.msg import Int32, String
|
||||
from sensor_msgs.msg import CompressedImage
|
||||
import cv2
|
||||
import time
|
||||
|
||||
|
||||
class TestPublisher(Node):
|
||||
def __init__(self):
|
||||
super().__init__("test_publisher")
|
||||
|
||||
# 声明参数
|
||||
self.declare_parameter(
|
||||
"image_path", "/home/sunrise/yiliao_ws/my_model/image.png"
|
||||
)
|
||||
self.declare_parameter("interval", 5.0) # 每 N 秒触发一次
|
||||
|
||||
image_path = self.get_parameter("image_path").value
|
||||
interval = self.get_parameter("interval").value
|
||||
|
||||
# 发布者
|
||||
self.image_pub = self.create_publisher(CompressedImage, "/image_mjpeg", 10)
|
||||
self.sign_pub = self.create_publisher(Int32, "/sign4return", 10)
|
||||
|
||||
# 订阅结果
|
||||
self.result_sub = self.create_subscription(
|
||||
String, "/vlm_result", self.result_callback, 10
|
||||
)
|
||||
|
||||
# 加载图片
|
||||
self.image_data = cv2.imread(image_path)
|
||||
if self.image_data is None:
|
||||
self.get_logger().error(f"无法读取图片: {image_path}")
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
|
||||
encode_params = [cv2.IMWRITE_JPEG_QUALITY, 90]
|
||||
_, jpeg_data = cv2.imencode(".jpg", self.image_data, encode_params)
|
||||
self.jpeg_bytes = jpeg_data.tobytes()
|
||||
self.get_logger().info(f"已加载图片: {image_path}")
|
||||
|
||||
# 定时器
|
||||
self.timer = self.create_timer(interval, self.timer_callback)
|
||||
self.get_logger().info(f"每 {interval}s 发送一次图片和触发信号")
|
||||
|
||||
def timer_callback(self):
|
||||
# 1. 发送压缩图像
|
||||
img_msg = CompressedImage()
|
||||
img_msg.header.stamp = self.get_clock().now().to_msg()
|
||||
img_msg.format = "jpeg"
|
||||
img_msg.data = self.jpeg_bytes
|
||||
self.image_pub.publish(img_msg)
|
||||
self.get_logger().info("已发送图片")
|
||||
|
||||
# 2. 等待一小段时间让订阅者收到图片
|
||||
time.sleep(1.0)
|
||||
|
||||
# 3. 发送触发信号 (sign=9)
|
||||
sign_msg = Int32()
|
||||
sign_msg.data = 9
|
||||
self.sign_pub.publish(sign_msg)
|
||||
self.get_logger().info("已发送触发信号 (sign=9),等待 VLM 结果...")
|
||||
|
||||
def result_callback(self, msg):
|
||||
self.get_logger().info(f"🔍 VLM 识别结果: {msg.data}")
|
||||
|
||||
|
||||
def main(args=None):
|
||||
rclpy.init(args=args)
|
||||
node = TestPublisher()
|
||||
try:
|
||||
rclpy.spin(node)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
node.destroy_node()
|
||||
rclpy.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
42
src/vlm_detect/vlm_detect/tts_node.py
Normal file
42
src/vlm_detect/vlm_detect/tts_node.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import rclpy, subprocess, requests, os
|
||||
from rclpy.node import Node
|
||||
from std_msgs.msg import String
|
||||
|
||||
VLM_HOST = "http://192.168.10.173:8000"
|
||||
# USB Audio Device (Card 1)
|
||||
AUDIO_SINK = "alsa_output.usb-C-Media_Electronics_Inc._USB_Audio_Device-00.analog-stereo"
|
||||
AUDIO_ENV = {**os.environ, "PULSE_SINK": AUDIO_SINK}
|
||||
|
||||
class TTSNode(Node):
|
||||
def __init__(self):
|
||||
super().__init__("tts_node")
|
||||
self.sub = self.create_subscription(String, "/vlm_result", self.callback, 10)
|
||||
self.get_logger().info("TTS 播报节点已启动 (USB Audio Device, edge-tts 自然语音)")
|
||||
|
||||
def callback(self, msg):
|
||||
text = msg.data
|
||||
self.get_logger().info(f"播报: {text}")
|
||||
try:
|
||||
resp = requests.post(f"{VLM_HOST}/v1/tts",
|
||||
json={"text": text, "voice": "zh-CN-XiaoxiaoNeural"}, timeout=60)
|
||||
mp3 = "/tmp/tts_out.mp3"
|
||||
with open(mp3, "wb") as f:
|
||||
f.write(resp.content)
|
||||
subprocess.Popen(["ffplay", "-nodisp", "-autoexit", mp3],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
env=AUDIO_ENV)
|
||||
except Exception as e:
|
||||
self.get_logger().error(f"TTS 失败,降级 espeak: {e}")
|
||||
subprocess.Popen(["espeak-ng", "-v", "zh", "-s", "150", text],
|
||||
env=AUDIO_ENV)
|
||||
|
||||
def main(args=None):
|
||||
rclpy.init(args=args)
|
||||
node = TTSNode()
|
||||
try: rclpy.spin(node)
|
||||
except KeyboardInterrupt: pass
|
||||
finally: node.destroy_node(); rclpy.shutdown()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -15,14 +15,14 @@ import numpy as np
|
||||
|
||||
class VLMProcessor(Node):
|
||||
def __init__(self):
|
||||
super().__init__('vlm_dtetct')
|
||||
super().__init__('vlm_detect')
|
||||
|
||||
# 初始化 OpenAI 客户端
|
||||
self.client = OpenAI(
|
||||
base_url="http://192.168.1.103:8000/v1",
|
||||
api_key="EMPTY"
|
||||
base_url="http://192.168.10.173:8000/v1", # 本地 API 地址
|
||||
api_key="EMPTY", # 不需要真实 API key
|
||||
)
|
||||
|
||||
|
||||
# ROS2 组件
|
||||
self.bridge = CvBridge()
|
||||
self.latest_image = None
|
||||
@@ -31,7 +31,7 @@ class VLMProcessor(Node):
|
||||
# 订阅图像话题
|
||||
self.image_sub = self.create_subscription(
|
||||
CompressedImage,
|
||||
'/image',
|
||||
'/image_mjpeg',
|
||||
self.image_callback,
|
||||
10
|
||||
)
|
||||
@@ -57,18 +57,20 @@ class VLMProcessor(Node):
|
||||
"""保存最新的图像"""
|
||||
with self.image_lock:
|
||||
try:
|
||||
# bridge = CvBridge()
|
||||
np_arr = np.frombuffer(msg.data, np.uint8)
|
||||
# 使用 OpenCV 解码图像
|
||||
cv_image = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
|
||||
# cv_image =bridge.imgmsg_to_cv2(msg,desired_encoding='bgr8')
|
||||
self.latest_image = cv_image
|
||||
self.get_logger().debug("recive picture")
|
||||
except Exception as e:
|
||||
self.get_logger().error("err picture")
|
||||
self.get_logger().error(f"err picture: {e}")
|
||||
|
||||
def sign_callback(self, msg):
|
||||
"""处理触发信号"""
|
||||
if msg.data == 9:
|
||||
self.get_logger().info("收到触发信号 (6),开始处理图像...")
|
||||
self.get_logger().info(f"收到触发信号 ({msg.data}),开始处理图像...")
|
||||
|
||||
# 检查是否有可用图像
|
||||
with self.image_lock:
|
||||
@@ -79,12 +81,12 @@ class VLMProcessor(Node):
|
||||
# 保存临时图像文件
|
||||
temp_path = "/tmp/vlm_temp_image.jpg"
|
||||
cv2.imwrite(temp_path, self.latest_image)
|
||||
self.get_logger().info("已保存临时图像}")
|
||||
self.get_logger().info(f"已保存临时图像: {temp_path}")
|
||||
|
||||
# 处理图像
|
||||
try:
|
||||
description = self.process_image(temp_path)
|
||||
self.get_logger().info(f"图像描述结果:")
|
||||
self.get_logger().info(f"图像描述结果: {description}")
|
||||
|
||||
# 发布结果
|
||||
result_msg = String()
|
||||
@@ -94,7 +96,7 @@ class VLMProcessor(Node):
|
||||
# 清理临时文件
|
||||
os.remove(temp_path)
|
||||
except Exception as e:
|
||||
self.get_logger().error(f"处理图像时出错:{e}")
|
||||
self.get_logger().error(f"处理图像时出错: {e}")
|
||||
|
||||
def process_image(self, image_path):
|
||||
"""使用 VLM 模型处理图像"""
|
||||
@@ -106,26 +108,26 @@ class VLMProcessor(Node):
|
||||
start_time = time.time()
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model="/home/yyh/vllm_test/model/InternVL3-1B",
|
||||
model="./OpenGVLab/InternVL3-1B/",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "描述图片中的动漫病人"},
|
||||
{"type": "text", "text": "描述图片中有一个病人的特征,字数控制在20字以内。"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
max_tokens=100
|
||||
max_tokens=100,
|
||||
)
|
||||
|
||||
processing_time = time.time() - start_time
|
||||
self.get_logger().info("VLM 处理耗时秒")
|
||||
self.get_logger().info(f"VLM 处理耗时 {processing_time:.1f}s")
|
||||
|
||||
return response.choices[0].message.content
|
||||
|
||||
|
||||
Reference in New Issue
Block a user