183 lines
6.8 KiB
Python
183 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
test.py
|
||
摄像头畸变校正效果测试 - 仅显示校正前后对比画面
|
||
"""
|
||
|
||
import cv2
|
||
import numpy as np
|
||
import argparse
|
||
import os
|
||
|
||
def is_rtsp_url(input_str):
|
||
"""检查输入是否为RTSP URL"""
|
||
return input_str.startswith('rtsp://')
|
||
|
||
def load_calibration_params(yaml_file):
|
||
"""从YAML文件加载标定参数"""
|
||
if not os.path.exists(yaml_file):
|
||
raise FileNotFoundError(f"标定文件 {yaml_file} 不存在")
|
||
|
||
fs = cv2.FileStorage(yaml_file, cv2.FILE_STORAGE_READ)
|
||
if not fs.isOpened():
|
||
raise IOError(f"无法打开标定文件 {yaml_file}")
|
||
|
||
# 读取参数
|
||
camera_matrix = fs.getNode("camera_matrix").mat()
|
||
dist_coeffs = fs.getNode("dist_coeffs").mat()
|
||
|
||
fs.release()
|
||
|
||
print(f"✓ 成功加载标定参数")
|
||
print(f" 相机矩阵:\n{camera_matrix}")
|
||
print(f" 畸变系数: {dist_coeffs.flatten()}")
|
||
|
||
return camera_matrix, dist_coeffs
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description='摄像头畸变校正效果测试')
|
||
parser.add_argument("-i", "--input", type=str, required=True,
|
||
help="输入源(摄像头设备索引、视频文件路径或RTSP URL)")
|
||
parser.add_argument("-c", "--calibration", type=str, required=True,
|
||
help="标定参数YAML文件路径")
|
||
parser.add_argument("-f", "--fisheye", action="store_true",
|
||
help="是否为鱼眼相机")
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 加载标定参数
|
||
try:
|
||
camera_matrix, dist_coeffs = load_calibration_params(args.calibration)
|
||
except Exception as e:
|
||
print(f"❌ 加载标定参数失败: {e}")
|
||
return
|
||
|
||
# 初始化视频源
|
||
if is_rtsp_url(args.input):
|
||
# RTSP流
|
||
cap = cv2.VideoCapture(args.input, cv2.CAP_FFMPEG)
|
||
try:
|
||
cap.set(cv2.CAP_PROP_FFMPEG_OPTION, "rtsp_transport", "tcp")
|
||
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
||
except:
|
||
pass
|
||
elif args.input.isdigit():
|
||
# 摄像头设备索引
|
||
cap = cv2.VideoCapture(int(args.input))
|
||
else:
|
||
# 视频文件
|
||
cap = cv2.VideoCapture(args.input)
|
||
|
||
if not cap.isOpened():
|
||
print(f"❌ 无法打开视频源: {args.input}")
|
||
return
|
||
|
||
# 获取实际分辨率
|
||
actual_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||
actual_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||
print(f"视频源分辨率: {actual_w}x{actual_h}")
|
||
|
||
print("✓ 准备开始校正")
|
||
print("按 'q' 键退出")
|
||
print("按 's' 键切换校正方法")
|
||
|
||
window_name = "畸变校正对比 - 原始(左) vs 校正后(右)"
|
||
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
|
||
|
||
# 校正方法选择
|
||
use_remap = True # 默认使用remap方法
|
||
|
||
frame_count = 0
|
||
while True:
|
||
ret, frame = cap.read()
|
||
if not ret:
|
||
print("读取帧失败")
|
||
# 对于RTSP流,尝试重新连接
|
||
if is_rtsp_url(args.input):
|
||
cap.release()
|
||
cap = cv2.VideoCapture(args.input, cv2.CAP_FFMPEG)
|
||
continue
|
||
break
|
||
|
||
frame_count += 1
|
||
|
||
# 应用畸变校正
|
||
try:
|
||
if args.fisheye:
|
||
if use_remap:
|
||
# 使用映射方法
|
||
new_camera_matrix = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(
|
||
camera_matrix, dist_coeffs, (actual_w, actual_h), np.eye(3), balance=0.8)
|
||
map1, map2 = cv2.fisheye.initUndistortRectifyMap(
|
||
camera_matrix, dist_coeffs, np.eye(3), new_camera_matrix,
|
||
(actual_w, actual_h), cv2.CV_16SC2)
|
||
undistorted = cv2.remap(frame, map1, map2,
|
||
interpolation=cv2.INTER_LINEAR,
|
||
borderMode=cv2.BORDER_CONSTANT)
|
||
else:
|
||
# 使用直接校正方法
|
||
undistorted = cv2.fisheye.undistortImage(frame, camera_matrix, dist_coeffs)
|
||
else:
|
||
# 普通相机校正
|
||
undistorted = cv2.undistort(frame, camera_matrix, dist_coeffs)
|
||
|
||
except Exception as e:
|
||
print(f"校正失败: {e}")
|
||
undistorted = frame.copy() # 失败时使用原图
|
||
|
||
# 检查校正后的图像是否有效
|
||
if undistorted is None or undistorted.size == 0:
|
||
print(f"警告: 第 {frame_count} 帧校正失败")
|
||
undistorted = frame.copy()
|
||
|
||
# 调整大小以便显示
|
||
max_height = 600
|
||
scale_orig = max_height / frame.shape[0]
|
||
orig_resized = cv2.resize(frame,
|
||
(int(frame.shape[1] * scale_orig),
|
||
int(frame.shape[0] * scale_orig)))
|
||
|
||
scale_undist = max_height / undistorted.shape[0]
|
||
undistorted_resized = cv2.resize(undistorted,
|
||
(int(undistorted.shape[1] * scale_undist),
|
||
int(undistorted.shape[0] * scale_undist)))
|
||
|
||
# 确保两个图像高度相同
|
||
min_height = min(orig_resized.shape[0], undistorted_resized.shape[0])
|
||
orig_resized = orig_resized[:min_height, :]
|
||
undistorted_resized = undistorted_resized[:min_height, :]
|
||
|
||
# 水平拼接显示对比
|
||
comparison = np.hstack((orig_resized, undistorted_resized))
|
||
|
||
# 添加文本说明
|
||
cv2.putText(comparison, "原始图像 (有畸变)",
|
||
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||
cv2.putText(comparison, "校正后图像",
|
||
(orig_resized.shape[1] + 10, 30),
|
||
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||
|
||
# 显示相机类型和校正方法
|
||
camera_type = "鱼眼相机" if args.fisheye else "普通相机"
|
||
method_type = "映射方法" if use_remap else "直接方法"
|
||
cv2.putText(comparison, f"类型: {camera_type} | 方法: {method_type}",
|
||
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
||
cv2.putText(comparison, "按 's' 切换方法 | 按 'q' 退出",
|
||
(10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
||
|
||
cv2.imshow(window_name, comparison)
|
||
|
||
key = cv2.waitKey(1) & 0xFF
|
||
if key == ord('q'):
|
||
break
|
||
elif key == ord('s'):
|
||
use_remap = not use_remap
|
||
method_name = "映射方法" if use_remap else "直接方法"
|
||
print(f"切换到 {method_name}")
|
||
|
||
cap.release()
|
||
cv2.destroyAllWindows()
|
||
print("测试完成")
|
||
|
||
if __name__ == "__main__":
|
||
main() |