diff --git a/README.md b/README.md
index 03d5403..aca05d3 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,8 @@
│ │ application_pkg │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ web_node (:8080) │ │ │
-│ │ │ - MJPEG 预览页 │ │ │
│ │ │ - POST /api/mode/switch │──┼──▶ /gimbal/mode │
+│ │ │ - POST /api/laser │ │ │
│ │ └───────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
│ │
@@ -50,8 +50,6 @@
| 阶段 | 话题 | 方向 | 消息类型 | 说明 |
|------|------|------|----------|------|
| 图像采集 | `/camera/image_raw` | camera_node → vision_node | `sensor_msgs/Image` | 原始相机图像 |
-| 视觉结果 | `/vision/result_image` | vision_node → web_node | `sensor_msgs/Image` | 带轮廓标注的结果图 |
-| 二值调试 | `/vision/binary_image` | vision_node → web_node | `sensor_msgs/Image` | 二值化调试图像 |
| 目标追踪 | `/vision/target` | vision_node → gimbal_node | `geometry_msgs/Point` | 归一化目标偏移 (x,y) |
| 模式切换 | `/gimbal/mode` | web_node → gimbal_node | `std_msgs/String` | auto/manual/debug |
| 手动控制 | `/cmd_gimbal` | 前端 → gimbal_node | `geometry_msgs/Twist` | yaw/pitch 角度 |
@@ -88,9 +86,6 @@ source install/setup.bash
# 启动全流程
ros2 launch bringup_pkg vision_pipeline.launch.py
-
-# 浏览器预览
-# http://<设备IP>:8080
```
详细使用方法见 [使用指南](docs/使用指南.md)。
diff --git a/docs/使用指南.md b/docs/使用指南.md
index a2115c9..ed1e856 100644
--- a/docs/使用指南.md
+++ b/docs/使用指南.md
@@ -160,13 +160,7 @@ ros2 topic pub /pid_config std_msgs/String \
参数立即生效,调试完毕后切回 auto 或 manual 模式。
-## 9. 查看视觉结果
-
-浏览器打开 `http://<设备IP>:8080`,可看到:
-- 原始图像 + 轮廓标注(result)
-- Otsu 二值化 + 闭运算结果(binary)
-
-## 10. 查看话题
+## 9. 查看话题
```bash
# 列出所有活跃话题
@@ -180,7 +174,7 @@ ros2 topic hz /vision/target
ros2 topic echo /vision/target
```
-## 11. 测试
+## 10. 测试
```bash
cd workspace
@@ -195,7 +189,7 @@ colcon test --packages-select gimbal_control_pkg
colcon test-result --all
```
-## 12. 故障排查
+## 11. 故障排查
| 现象 | 检查项 |
|------|--------|
diff --git a/workspace/src/application_pkg/application_pkg/web_node.py b/workspace/src/application_pkg/application_pkg/web_node.py
index 79e07df..1505aec 100755
--- a/workspace/src/application_pkg/application_pkg/web_node.py
+++ b/workspace/src/application_pkg/application_pkg/web_node.py
@@ -3,88 +3,20 @@ import json
import socket
import select
import socketserver
-import cv2
import rclpy
from rclpy.node import Node
-from sensor_msgs.msg import Image
from std_msgs.msg import String
-from cv_bridge import CvBridge
from http.server import HTTPServer, BaseHTTPRequestHandler
-_latest_jpeg = None
-
-_HTML_PAGE = """
-
-
-
-
-Vision Pipeline
-
-
-
- Vision Pipeline Results
-
-
-
Result Image (contours)
-

-
-
-
Binary (Otsu + Close)
-

-
-
- Live
-
-
-"""
-
-
_mode_publisher = None
-class _ImageHandler(BaseHTTPRequestHandler):
+class _APIHandler(BaseHTTPRequestHandler):
def do_GET(self):
- path = self.path.split('?')[0]
- if path == '/' or path == '/index.html':
- self.send_response(200)
- self.send_header('Content-type', 'text/html; charset=utf-8')
- self.end_headers()
- self.wfile.write(_HTML_PAGE.encode())
- elif path in ('/result', '/binary'):
- self._serve_image(path.lstrip('/'))
- else:
- self.send_response(404)
- self.end_headers()
+ self.send_response(404)
+ self.end_headers()
def do_POST(self):
path = self.path.split('?')[0]
@@ -160,22 +92,6 @@ class _ImageHandler(BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(f'{{"status":"ok","laser":"{action}"}}'.encode())
- def _serve_image(self, key):
- global _latest_jpeg
- jpeg = _latest_jpeg.get(key) if _latest_jpeg else None
- if jpeg is None:
- self.send_response(204)
- self.end_headers()
- return
- self.send_response(200)
- self.send_header('Content-type', 'image/jpeg')
- self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
- self.end_headers()
- try:
- self.wfile.write(jpeg)
- except (BrokenPipeError, ConnectionResetError):
- pass
-
def handle_error(self, request, client_address):
pass
@@ -191,56 +107,32 @@ class WebNode(Node):
def __init__(self, port=8080):
super().__init__('web_node')
global _mode_publisher
- self._bridge = CvBridge()
-
- self._result_sub = self.create_subscription(
- Image, '/vision/result_image', self._make_jpeg_callback('result'), 10
- )
- self._binary_sub = self.create_subscription(
- Image, '/vision/binary_image', self._make_jpeg_callback('binary'), 10
- )
self._mode_publisher = self.create_publisher(
String, '/gimbal/mode', 10
)
_mode_publisher = self._mode_publisher
- self._server = _ThreadingHTTPServer(('', port), _ImageHandler)
+ self._server = _ThreadingHTTPServer(('', port), _APIHandler)
self._server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._server.socket.setblocking(False)
self._server.timeout = 0
- # 定时器在主线程中轮询 accept,连接交给工作线程处理
self._timer = self.create_timer(0.01, self._handle_http_requests)
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
self.get_logger().info(
- f'Web server on http://{ip}:{port} (http://localhost:{port})'
+ f'API server on http://{ip}:{port}'
)
def _handle_http_requests(self):
- # 一次 tick 内处理所有积压连接
for _ in range(20):
try:
self._server.handle_request()
except Exception:
break
- def _make_jpeg_callback(self, key):
- def callback(msg):
- global _latest_jpeg
- try:
- cv_image = self._bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
- _, jpeg = cv2.imencode('.jpg', cv_image,
- [cv2.IMWRITE_JPEG_QUALITY, 85])
- if _latest_jpeg is None:
- _latest_jpeg = {}
- _latest_jpeg[key] = jpeg.tobytes()
- except Exception as e:
- self.get_logger().error(f'JPEG encode error ({key}): {e}')
- return callback
-
def destroy_node(self):
self._server.shutdown()
super().destroy_node()
diff --git a/workspace/src/application_pkg/package.xml b/workspace/src/application_pkg/package.xml
index 0583f12..e56ce9e 100755
--- a/workspace/src/application_pkg/package.xml
+++ b/workspace/src/application_pkg/package.xml
@@ -8,8 +8,6 @@
TODO: License declaration
rclpy
- sensor_msgs
- cv_bridge
std_msgs
ament_copyright