4.3 视觉控制中的坐标与滤波
总目录:1.1 OpenCV介绍与学习路线 | 第四章:把知识连成项目 | 编号:4.3
← 上一节:4.2 摄像头检测与调参 | 返回总目录:1.1 OpenCV介绍与学习路线
4.3 视觉控制中的坐标与滤波
视觉检测给出 (cx, cy),云台却需要“往哪转、转多少”。中间隔着坐标系、镜头投影、控制方向和时延。这个地方一旦符号弄反,云台就会非常努力地逃离目标。
一、先分清图像、相机、云台和世界坐标
至少有四套概念:
图像坐标:像素 (x, y),右为正、下为正
相机坐标:以镜头为基准的三维方向
云台角度:yaw / pitch 的编码与正方向
世界坐标:固定在场地或靶面上的位置
它们不能靠“看起来差不多”直接相减。至少要做方向标定:
- 云台 yaw 增大时,画面中的固定目标向左还是向右?
- pitch 增大时,目标向上还是向下?
- 摄像头是否倒装、镜像或旋转?
- 单片机角度单位是度、弧度还是编码器计数?
二、从目标中心得到像素误差
画面中心:
height, width = frame.shape[:2]
image_center = (width / 2, height / 2)
目标误差:
error_x = target_x - image_center[0]
error_y = target_y - image_center[1]
按图像坐标:
error_x > 0 → 目标在画面右边
error_y > 0 → 目标在画面下边
发送给云台前是否取负,要由刚才的方向实验决定。
加入死区,避免中心附近来回抖:
def deadband(value, width):
if abs(value) <= width:
return 0.0
return value - np.sign(value) * width
三、像素怎样粗略换成角度
若只知道水平视场角 HFOV,可先做小角度近似:
degrees_per_pixel_x = hfov_deg / width
angle_error_x = error_x * degrees_per_pixel_x
垂直方向同理。这个线性关系在画面中心附近较合理,越靠边误差越明显。
更准确的针孔模型使用焦距像素值:
angle_error_x = np.degrees(np.arctan2(error_x, fx))
angle_error_y = np.degrees(np.arctan2(error_y, fy))
fx、fy 来自相机标定矩阵。镜头畸变明显时,应先标定和去畸变。
四、一阶低通:给误差信号加减震器
一阶低通公式:
filtered = α × current + (1 - α) × previous
α接近 1:更相信新值,反应快,平滑弱;α接近 0:更相信旧值,平滑强,延迟大。
class LowPassFilter:
def __init__(self, alpha):
if not 0 < alpha <= 1:
raise ValueError("alpha 必须在 (0, 1] 内")
self.alpha = alpha
self.value = None
def update(self, current):
current = float(current)
if self.value is None:
self.value = current
else:
self.value = (
self.alpha * current
+ (1 - self.alpha) * self.value
)
return self.value
def reset(self):
self.value = None
用 None 初始化,第一帧直接采用当前值,避免原笔记从 0 开始造成启动偏差。
帧率变化明显时,用时间常数比固定 alpha 更好解释:
alpha = dt / (tau + dt)
tau 越大,平滑越强。
五、滤波为什么也可能让系统更差
滤波能压噪声,却会引入滞后。闭环控制里,画面本来就晚于真实运动,再加很强滤波,控制器可能一直追着过去的位置跑。
震荡不一定只靠滤波解决,还要检查:
- 控制增益是否过大;
- 符号是否反了;
- 摄像头与执行器延迟;
- 串口命令是否积压;
- 云台机械回差;
- 目标检测是否间歇跳到干扰物;
- 相机在云台上运动造成的画面反馈。
先修符号和时延
↓
再降低控制增益、加限幅与死区
↓
最后用适量滤波处理剩余噪声
图像的高斯滤波处理空间邻域;这里的一阶低通处理时间序列。都叫滤波,但对象完全不同。
六、固定物理中心与圆周轨迹
若要在距离 D 的平面上画物理半径 R 的圆,对应角半径可写为:
radius_angle = np.degrees(np.arctan2(R, D))
在世界或云台角度坐标里生成轨迹更清楚:
target_yaw = center_yaw + radius_angle * np.cos(omega * t)
target_pitch = center_pitch + radius_angle * np.sin(omega * t)
然后再让控制器追踪目标角度,而不是先在像素平面随便画个圆,再假设它一定对应真实世界的圆。
若必须预测固定世界点在当前画面中的像素位置,需要:
- 当前 yaw / pitch;
- 相机与云台轴之间的外参;
- 相机内参
fx, fy, cx, cy; - 坐标轴方向约定;
- 必要时考虑平移和镜头畸变。
原笔记中的 origin_x = Ox - yaw * K 可以作为小范围实验模型,但不应直接当完整相机投影模型。
七、一条更稳妥的控制链
检测目标中心
↓
检查目标是否可信、是否连续
↓
像素误差 → 角度误差
↓
死区 + 限幅 + 适量低通
↓
控制器计算命令
↓
发送并记录时间戳
↓
观察真实响应,重新调参
限幅示例:
command = np.clip(kp * filtered_error, -max_command, max_command)
目标丢失时不要继续拿最后一个误差无限控制:
if target is None:
filter_x.reset()
filter_y.reset()
command_x = 0.0
command_y = 0.0
到这里,OpenCV 入门的主线就走完了:像素怎样进来、怎样被分割成目标、怎样变成轮廓特征、又怎样交给控制系统。以后遇到新任务,先画流水线,再逐层替换工具,会比到处复制一整段代码可靠得多。
← 上一节:4.2 摄像头检测与调参 | 返回总目录:1.1 OpenCV介绍与学习路线
留言区
不收集任何个人信息;昵称只绑定浏览器里的随机 Cookie。