机械臂摄像头

This commit is contained in:
ywb
2026-05-21 19:44:25 +08:00
parent 56729c23e8
commit 457f32919c
3 changed files with 44 additions and 18 deletions
+8 -18
View File
@@ -1130,26 +1130,16 @@ def api_camera_capture():
@app.route("/api/camera/arm_refresh") @app.route("/api/camera/arm_refresh")
def api_arm_camera_refresh(): def api_arm_camera_refresh():
"""从机械臂拉一张 JPEG流式读第一个完整帧""" """从机械臂拉一张 JPEG请求 snapshot 端点,简单 HTTP GET"""
import requests import requests
try: try:
r = requests.get(ARM_CAMERA_CONFIG["url"], stream=True, timeout=8) r = requests.get(ARM_CAMERA_CONFIG.get("snapshot_url", ARM_CAMERA_CONFIG["url"]), timeout=8)
if r.status_code != 200: if r.status_code == 200 and r.content:
return "", 404 resp = Response(r.content, mimetype="image/jpeg")
data = b"" resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0"
for chunk in r.iter_content(chunk_size=8192): resp.headers["Pragma"] = "no-cache"
data += chunk resp.headers["Expires"] = "0"
# 在累积数据中找 JPEG 完整帧 return resp
s = data.find(b"\xff\xd8")
e = data.find(b"\xff\xd9", s + 2) if s >= 0 else -1
if s >= 0 and e > s:
r.close()
return Response(data[s:e+2], mimetype="image/jpeg")
# 累积超过 5MB 还没找到完整帧,说明流异常,放弃
if len(data) > 5 * 1024 * 1024:
r.close()
return "", 404
r.close()
return "", 404 return "", 404
except Exception as ex: except Exception as ex:
logger.error(f"arm_refresh 失败: {ex}") logger.error(f"arm_refresh 失败: {ex}")
+1
View File
@@ -40,6 +40,7 @@ CAMERA_CONFIG = {
# ========== 机械臂摄像头流 ========== # ========== 机械臂摄像头流 ==========
ARM_CAMERA_CONFIG = { ARM_CAMERA_CONFIG = {
"url": "http://192.168.110.164:5003/api/camera/preview", "url": "http://192.168.110.164:5003/api/camera/preview",
"snapshot_url": "http://192.168.110.164:5003/api/camera/snapshot",
} }
# ========== HTTP 上传 ========== # ========== HTTP 上传 ==========
+35
View File
@@ -45,6 +45,7 @@ def _ensure_ffmpeg():
"ffmpeg", "ffmpeg",
"-f", "v4l2", "-f", "v4l2",
"-input_format", "mjpeg", "-input_format", "mjpeg",
"-re",
"-i", f"/dev/video{ARM_CAMERA_INDEX}", "-i", f"/dev/video{ARM_CAMERA_INDEX}",
"-vf", "rotate=PI", "-vf", "rotate=PI",
"-q:v", "8", "-q:v", "8",
@@ -103,6 +104,40 @@ def arm_camera_restart():
_ensure_ffmpeg() _ensure_ffmpeg()
return jsonify({"ok": True}) return jsonify({"ok": True})
@arm_video_app.route("/api/camera/snapshot")
def arm_camera_snapshot():
"""机械臂摄像头单帧 JPEG — pkill -9 强杀旧 ffmpeg,再临时抓一帧"""
import subprocess
global _ffmpeg_proc
# 用 pkill -9 强杀所有 ffmpeg 进程,释放 /dev/video0
subprocess.run(["pkill", "-9", "-f", "ffmpeg"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=3)
time.sleep(0.3)
_ffmpeg_proc = None
proc = subprocess.run(
[
"ffmpeg",
"-f", "v4l2",
"-input_format", "mjpeg",
"-i", f"/dev/video{ARM_CAMERA_INDEX}",
"-vf", "rotate=PI",
"-vframes", "1",
"-q:v", "8",
"-f", "mjpeg",
"pipe:1"
],
stdout=subprocess.PIPE,
timeout=5,
stderr=subprocess.DEVNULL
)
if proc.returncode == 0 and proc.stdout:
r = Response(proc.stdout, mimetype="image/jpeg")
r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0"
r.headers["Pragma"] = "no-cache"
r.headers["Expires"] = "0"
return r
logger.warning(f"ffmpeg snapshot failed: rc={proc.returncode}")
return "", 500
# ========== RoboFlow 630 Socket API 客户端 ========== # ========== RoboFlow 630 Socket API 客户端 ==========
class RoboFlowClient: class RoboFlowClient: