cmvr-es/python/vision_servo/get_chessboard_position.py
2025-08-21 13:56:39 +08:00

116 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
从 RealSense 相机中获取一帧对齐后的彩色 / 深度图,
检测棋盘格左上角角点,并反投影到相机坐标系 (m)。
"""
import cv2
import time
import numpy as np
import pyrealsense2 as rs
# ========================= 1. 管线启动 / 关闭 ============================== #
def start_pipeline(serial: str, depth_size=(640, 480), color_size=(640, 480), fps: int = 30):
pipeline = rs.pipeline()
cfg = rs.config()
cfg.enable_device(serial)
# 正确的参数顺序: width, height, format, fps
cfg.enable_stream(rs.stream.depth, depth_size[0], depth_size[1], rs.format.z16, fps)
cfg.enable_stream(rs.stream.color, color_size[0], color_size[1], rs.format.bgr8, fps)
pipeline_profile = pipeline.start(cfg)
depth_intr = pipeline_profile.get_stream(rs.stream.depth).as_video_stream_profile().get_intrinsics()
align = rs.align(rs.stream.depth)
return pipeline, align, depth_intr
def stop_pipeline(pipeline: rs.pipeline):
pipeline.stop()
# ========================= 2. 获取对齐后的单帧 ============================ #
def get_aligned_frames(pipeline: rs.pipeline, align: rs.align, warmup: int = 10, timeout_ms: int = 5000):
for _ in range(warmup):
pipeline.wait_for_frames()
# 等待一帧
for _ in range(3): # 最多重试 3 次
try:
frames = pipeline.wait_for_frames(timeout_ms=timeout_ms)
aligned = align.process(frames)
return aligned.get_color_frame(), aligned.get_depth_frame()
except RuntimeError:
print("⚠️ 等待帧超时,重试中...")
time.sleep(1)
raise RuntimeError("连续超时,无法获取有效帧")
# ========================= 3. 计算目标 3-D 坐标 =========================== #
def chessboard_lefttop_position(color_frame, depth_frame, depth_intr, pattern_size=(8, 11), avg_window=3, vis_path=None): # ← 额外参数:保存路径
"""
返回左上角角点相机坐标 (X,Y,Z) [m]。
若 vis_path 不为 None则在彩色图上标出中心和角点并保存到该路径。
"""
color_img = np.asanyarray(color_frame.get_data()).copy()
gray = cv2.cvtColor(color_img, cv2.COLOR_BGR2GRAY)
ok, corners = cv2.findChessboardCorners(
gray, pattern_size,
flags=cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE
)
if not ok:
raise RuntimeError("未检测到棋盘格")
cv2.cornerSubPix(
gray, corners, winSize=(5, 5), zeroZone=(-1, -1),
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-3)
)
# --------- 取第 0 个角点像素坐标 ---------
u, v = corners[0].ravel()
# --------- 可视化:画中心十字 & 左上角红点 ---------
if vis_path is not None:
cx, cy = depth_intr.ppx, depth_intr.ppy # 主点
cv2.drawMarker(color_img, (int(cx), int(cy)), (0, 255, 0),
markerType=cv2.MARKER_CROSS, markerSize=20, thickness=2)
cv2.circle(color_img, (int(u), int(v)), 6, (0, 0, 255), -1)
cv2.imwrite(vis_path, color_img) # ← 保存文件
print(f"✓ 已保存标记图到 {vis_path}")
# … 以下深度中值滤波 + 反投影保持不变 … ----------------------------
width, height = depth_frame.get_width(), depth_frame.get_height()
depths = [depth_frame.get_distance(int(round(u + du)), int(round(v + dv)))
for du in range(-avg_window, avg_window + 1)
for dv in range(-avg_window, avg_window + 1)
if 0 <= int(round(u + du)) < width and
0 <= int(round(v + dv)) < height and
depth_frame.get_distance(int(round(u + du)), int(round(v + dv))) > 0]
if not depths:
raise RuntimeError("角点深度无效")
depth = float(np.median(depths))
X, Y, Z = rs.rs2_deproject_pixel_to_point(depth_intr, [u, v], depth)
return np.array([X, Y, Z], dtype=np.float32)
# ========================= 4. 主程序入口 ================================= #
def main(serial="243122075614", pattern_size=(8, 11), avg_window=3):
pipeline, align, depth_intr = start_pipeline(serial)
try:
color_f, depth_f = get_aligned_frames(pipeline, align)
pos = chessboard_lefttop_position(
color_f, depth_f, depth_intr,
pattern_size=pattern_size, avg_window=avg_window,
vis_path="chessboard_marked.png"
)
print("左上角 3-D 坐标 (m):", pos)
finally:
stop_pipeline(pipeline)
if __name__ == "__main__":
main()