cmvr-es/python/vision_servo/get_chessboard_position.py

116 lines
4.6 KiB
Python
Raw Normal View History

2025-08-21 13:56:39 +08:00
#!/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()