提取图中激光位置的坐标

本文介绍了一种使用Python和图像处理库识别显微镜下激光点位置的方法。通过将图像转换为灰度并进一步转为二值图像,最终利用堆算法找到激光点坐标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述
如上图,本图是在显微镜下拍摄的图片,图中的亮点是激光所在的位置,我本来是想用C++和OpenCV来找出图中激光所在的位置,后来在老师的点拨下,采用了Python处理。把图片看成一个矩阵,这样操作起来更简单,也更准确。
言归正传,直接看程序。
“””
Created on Thu Jul 27 15:36:22 2017

@author: WYH
“”“

from PIL import Image
import numpy as np
import pylab as pl
import heapq
name=”C:/Users/WYH/Desktop/7.png”
im=np.array(Image.open(name).convert(‘L’))
pl.gray() #把原图转化为灰度图
B1=np.float64(im) #把像素值转换为浮点数
B2=np.where(B1>248,1,0) #将灰度图转化为二值图像,阈值为248

C=list(np.sum(B2,axis=0))
D=list(np.sum(B2,axis=1)) #将每一行求和得数组1,将每一列求和得数组2
Xmax1=heapq.nlargest(1,C)
x=C.index(Xmax1) #求出最大值处的坐标
Ymax1=heapq.nlargest(1,D)
y=D.index(Ymax1)
print(“这一点的x、y坐标分别为 “,x,y)
print(B1[y,x])
pl.imshow(B2)
pl.show()

这段代码用Python运行结果如下:
二值图像
运行结果
Python是如此的方便,如此的有趣,希望在以后的学习中能把它熟练掌握。

<think>我们有一个项目,使用OpenMV控制云台舵机,并用激光绘制矩形。根据引用,我们知道系统通过OpenMV识别红色激光坐标,然后主控控制云台带动绿色激光追踪。现在我们要绘制矩形,意味着我们需要控制绿色激光按照矩形路径移动。 思路: 1. 我们需要定义矩形的四个顶点(在二维平面上,假设在摄像头视野范围内)。 2. 控制云台舵机(水平方向和垂直方向)将激光点依次移动到这四个顶点,并在每个顶点短暂停留(或者连续移动,但为了绘制清晰,可能需要控制移动速度)。 3. 由于云台有两个舵机(水平舵机和垂直舵机),我们需要分别控制两个舵机的角度,从而控制激光点的位置。 根据引用[2],我们看到了控制水平方向舵机的函数`Spinnig_Level`,它通过改变PWM的占空比(即`Oc_L`)来控制舵机角度。类似地,应该也有一个控制垂直方向舵机的函数。 步骤: 1. 确定矩形在坐标系中的四个顶点坐标(例如,用(x,y)表示,x和y的范围取决于摄像头的分辨率)。 2. 将坐标转换为舵机角度(或直接转换为PWM值)。这需要标定:即建立坐标与舵机角度的映射关系。 3. 按照顺序(比如顺时针)依次移动到四个顶点。 由于引用中提到,当红色激光和绿色激光重合时会有干扰,所以我们在绘制矩形时,应该只使用绿色激光(即我们控制的激光)进行绘制,而红色激光可能作为目标(但这里我们不需要追踪,而是绘制)。因此,在绘制矩形时,我们不需要红色激光,可以关闭红色激光或者忽略它。 但是,根据用户需求,我们是要用激光绘制矩形,所以这里我们假设绿色激光就是我们用来绘制的激光。我们只需要控制绿色激光按照矩形路径移动即可。 具体实现: 1. 标定:我们需要知道坐标(x,y)对应的两个舵机的PWM值。这可以通过实验获得。例如,我们可以将激光点移动到已知坐标位置,记录下此时的舵机PWM值,然后建立映射关系(比如线性插值)。 2. 定义矩形:假设矩形的左上角为(x1,y1),右上角(x2,y1),右下角(x2,y2),左下角(x1,y2)。注意,坐标系通常是左上角为原点(0,0),向右为x正方向,向下为y正方向。 3. 编写函数,将坐标(x,y)转换为两个舵机的PWM值(水平舵机PWM_h,垂直舵机PWM_v)。假设我们通过标定得到: PWM_h = a0 + a1 * x + a2 * y (或者更复杂的映射,但通常可以分离开,即水平舵机主要与x有关,垂直舵机主要与y有关) 实际上,由于舵机安装位置,可能两者会有耦合,但为了简单,我们假设是线性的且独立的。 4. 移动激光点:依次将四个顶点坐标转换为PWM值,然后设置舵机转动到相应位置。移动过程中,为了绘制连续,可以在两个点之间进行插值(即中间点),但简单的矩形也可以直接跳变到每个顶点(这样画出来的是四条直线)。 5. 为了连续绘制,我们可以将矩形路径分解为多个小线段,然后逐步移动。 然而,引用[2]中只给出了水平舵机的控制函数,我们还需要垂直舵机的控制函数。类似地,我们可以写一个垂直方向的控制函数。 例如,垂直方向控制函数可能如下(假设舵机控制类似): ```c void Spinnig_Vertical(int diff) { if(diff<0) { Oc_Vp[0]=Oc_V=(Oc_V+diff)<660?660:(Oc_V+diff); } else if(diff>0) { Oc_Vp[0]=Oc_V=(Oc_V+diff)>840?840:(Oc_V+diff); } TIM_SetCompare2(TIM1,Oc_V); // 假设使用TIM1的通道2 int i; for(i=3;i>0;i--)Oc_Vp[i]=Oc_Vp[i-1]; } ``` 但是,上面的函数是通过改变一个增量来控制舵机。我们可能需要一个直接设置目标PWM值的函数。因此,我们可以修改为: 水平舵机控制函数(直接设置目标值): ```c void Set_Level(int target) { // 限制目标值在有效范围内 if(target < 660) target = 660; if(target > 840) target = 840; Oc_L = target; TIM_SetCompare1(TIM1, Oc_L); // 更新历史记录(如果需要的话) int i; for(i=3; i>0; i--) { Oc_Lp[i] = Oc_Lp[i-1]; } Oc_Lp[0] = Oc_L; } ``` 同样,垂直舵机控制函数: ```c void Set_Vertical(int target) { if(target < 660) target = 660; if(target > 840) target = 840; Oc_V = target; TIM_SetCompare2(TIM1, Oc_V); // 假设通道2是垂直舵机 int i; for(i=3; i>0; i--) { Oc_Vp[i] = Oc_Vp[i-1]; } Oc_Vp[0] = Oc_V; } ``` 然后,我们编写一个函数,将坐标(x,y)转换为两个舵机的PWM值(水平PWM_h,垂直PWM_v): 假设我们通过标定得到: 水平方向:x=0时,PWM_h=840;x=像宽度(比如320)时,PWM_h=660。那么映射关系为: PWM_h = 840 - (x * (840-660))/320.0 垂直方向:y=0时,PWM_v=660;y=像高度(比如240)时,PWM_v=840。那么映射关系为: PWM_v = 660 + (y * (840-660))/240.0 注意:这个映射关系取决于舵机的安装方向,可能需要调整。 因此,我们可以写一个转换函数: ```c void ImageToPWM(int x, int y, int *pwm_h, int *pwm_v) { // 假设像宽度320,高度240 float scale_h = (840.0 - 660.0) / 320.0; float scale_v = (840.0 - 660.0) / 240.0; *pwm_h = 840 - (int)(x * scale_h); *pwm_v = 660 + (int)(y * scale_v); } ``` 注意:这里我们假设水平舵机从左到右,PWM值从840减小到660;垂直舵机从上到下,PWM值从660增加到840。实际情况可能不同,需要根据标定调整。 然后,绘制矩形的函数: 假设矩形四个顶点:A(x1,y1), B(x2,y1), C(x2,y2), D(x1,y2) 步骤: 1. 从当前点移动到A点 2. 移动到B点 3. 移动到C点 4. 移动到D点 5. 回到A点(闭合矩形) 但是,为了绘制连续,我们可以在相邻两点之间进行插值,即计算两点之间的线段,然后以一定步长移动。 这里,我们采用简单的直线插补算法(线性插值): 例如,从点A(x1,y1)移动到点B(x2,y1),水平方向从x1到x2,垂直方向不变(y1)。我们可以将x从x1到x2分成若干步,每一步计算对应的PWM值,然后设置舵机。 具体实现(伪代码): ```c void DrawLine(int x1, int y1, int x2, int y2, int steps) { float dx = (x2 - x1) / (float)steps; float dy = (y2 - y1) / (float)steps; float x = x1, y = y1; int pwm_h, pwm_v; for (int i=0; i<steps; i++) { x += dx; y += dy; ImageToPWM((int)x, (int)y, &pwm_h, &pwm_v); Set_Level(pwm_h); Set_Vertical(pwm_v); delay_ms(10); // 等待一段时间,让舵机运动到指定位置 } } ``` 然后,绘制矩形: ```c void DrawRectangle(int x1, int y1, int x2, int y2) { int steps = 50; // 每条边分成50步 // 从A到B DrawLine(x1, y1, x2, y1, steps); // 从B到C DrawLine(x2, y1, x2, y2, steps); // 从C到D DrawLine(x2, y2, x1, y2, steps); // 从D到A DrawLine(x1, y2, x1, y1, steps); } ``` 注意:这里我们假设矩形是水平垂直的。如果矩形是倾斜的,则需要四个点的坐标。 实际使用中,我们需要考虑舵机的响应速度。如果移动太快,舵机可能跟不上,所以需要调整步数和延时时间。 另外,由于我们使用OpenMV,上述代码应该是运行在主控(如MSP432)上,OpenMV负责识别坐标(但在这个任务中,我们不需要识别,而是主动控制绘制)。因此,整个控制逻辑在主控上完成。 但是,用户要求使用OpenMV控制云台舵机。实际上,OpenMV本身也可以直接控制舵机(通过其IO口输出PWM)。所以,如果主控是OpenMV,那么我们可以将上述代码移植到OpenMV的Python环境中。 OpenMV控制舵机的方法: OpenMV有PWM输出功能,可以控制舵机。具体可参考OpenMV官方文档。 在OpenMV上使用Python实现: 1. 首先,初始化两个舵机引脚(例如,P7和P8分别控制水平和垂直舵机)。 2. 定义设置舵机角度的函数(实际上,舵机控制是通过设置PWM的占空比,而占空比与角度有关)。通常,舵机控制信号周期为20ms,占空比0.5ms-2.5ms对应0-180度。所以,我们可以将舵机角度转换为占空比。 例如: ```python def set_servo(servo, angle): # 假设舵机角度范围0-180度,对应的占空比0.5ms-2.5ms # 周期20ms,所以占空比 = (0.5 + angle*(2.0/180.0)) / 20 * 100 # 但是OpenMV的PWM输出占空比是0-100,所以: duty_cycle = (0.5 + angle * 2.0 / 180.0) / 20 * 100 servo.duty(duty_cycle) ``` 但是,我们上面使用的是PWM值(660-840),这可能是特定于之前主控的(可能是特定的定时器设置)。在OpenMV上,我们需要重新标定,或者使用角度控制。 另一种方法是,我们直接使用角度控制。那么我们需要知道坐标(x,y)对应的舵机角度(水平角度和垂直角度)。这同样需要标定。 为简化,我们假设在OpenMV上,我们使用角度控制: 定义函数,将坐标转换为两个舵机的角度: ```python def image_to_angle(x, y, img_width=320, img_height=240): # 假设水平方向:x=0 -> 角度0,x=img_width -> 角度180 # 垂直方向:y=0 -> 角度0,y=img_height -> 角度180 angle_h = x * 180.0 / img_width angle_v = y * 180.0 / img_height return angle_h, angle_v ``` 然后,绘制直线和矩形的函数与上述类似,只是用角度控制。 在OpenMV上实现绘制矩形的代码框架: ```python import time from pyb import Pin, Timer # 初始化舵机引脚和定时器 # 假设水平舵机在P7(定时器3,通道1),垂直舵机在P8(定时器3,通道2) tim = Timer(3, freq=50) # 50Hz,周期20ms servo_h = tim.channel(1, Timer.PWM, pin=Pin("P7")) servo_v = tim.channel(2, Timer.PWM, pin=Pin("P8")) def set_servo(servo, angle): # 将角度转换为占空比 # 角度范围0~180度,对应的脉冲宽度0.5ms~2.5ms pulse_width = 0.5 + (angle / 180) * 2 # 脉冲宽度(ms) duty_cycle = (pulse_width / 20) * 100 # 占空比0-100 servo.pulse_width_percent(duty_cycle) def image_to_angle(x, y, img_width=320, img_height=240): angle_h = x * 180.0 / img_width angle_v = y * 180.0 / img_height return angle_h, angle_v def draw_line(x1, y1, x2, y2, steps=50): dx = (x2 - x1) / steps dy = (y2 - y1) / steps x, y = x1, y1 for i in range(steps): x += dx y += dy angle_h, angle_v = image_to_angle(int(x), int(y)) set_servo(servo_h, angle_h) set_servo(servo_v, angle_v) time.sleep_ms(10) def draw_rectangle(x1, y1, x2, y2): # 矩形的四个顶点 # 注意:这里我们假设矩形是水平垂直的,并且(x1,y1)是左上角,(x2,y2)是右下角 draw_line(x1, y1, x2, y1) # 上边 draw_line(x2, y1, x2, y2) # 右边 draw_line(x2, y2, x1, y2) # 下边 draw_line(x1, y2, x1, y1) # 左边 # 调用绘制矩形,例如:从(50,50)到(270,190) draw_rectangle(50,50,270,190) ``` 注意:上面的代码是示例,实际使用中需要根据硬件调整舵机的角度映射(可能不是0-180度,而且舵机的安装方向可能不同,需要调整正方向)。 另外,由于舵机的运动范围有限,可能无法覆盖整个像区域,所以需要根据实际舵机的运动范围来调整映射关系。 总结:使用OpenMV控制云台舵机绘制矩形,需要将矩形路径分解为四个边,然后控制两个舵机按照路径移动。关键点在于坐标到舵机角度的转换,以及舵机的精确控制。 以上代码仅为示例,实际应用中需要根据硬件进行调试。 相关问题: 1. 如何标定坐标与舵机角度的关系? 2. 如何提高激光绘制矩形的精度? 3. 除了矩形,如何绘制其他形状(如圆形)?</think>### OpenMV控制云台舵机激光绘制矩形的实现方案 #### 1. **系统架构设计** - **核心组件**: - OpenMV摄像头:识别目标/提供坐标反馈 - 二维舵机云台:控制激光的水平和垂直移动 - 激光发射器:固定在云台上的绿色激光 - 主控制器(如STM32):处理坐标数据并生成舵机控制信号[^1] - **工作流程**: ```mermaid graph TD A[OpenMV像识别] --> B[提取矩形顶点坐标] B --> C[坐标转换计算] C --> D[生成舵机PWM信号] D --> E[云台带动激光移动] E --> F[完成矩形绘制] ``` #### 2. **关键实现步骤** ##### (1) 矩形坐标定义与转换 - 定义矩形顶点(坐标系): $$P_1(x_1,y_1),\ P_2(x_2,y_1),\ P_3(x_2,y_2),\ P_4(x_1,y_2)$$ - 坐标转换公式(坐标→舵机角度): $$ \begin{cases} \theta_h = k_x \cdot x + b_x \\ \theta_v = k_y \cdot y + b_y \end{cases} $$ 其中 $k_x, k_y$ 为标定系数,需通过实验确定[^2]。 ##### (2) 舵机控制核心代码 ```python # OpenMV Python 控制示例 import pyb from pyb import Servo # 初始化舵机 servo_x = Servo(1) # 水平舵机(P7) servo_y = Servo(2) # 垂直舵机(P8) # 矩形绘制函数 def draw_rectangle(points, delay=300): # 顺序移动: P1→P2→P3→P4→P1 for (x,y) in points + [points[0]]: # 坐标转换 (需根据标定调整参数) angle_x = int(45 + x * 0.2) # 示例转换公式 angle_y = int(30 + y * 0.15) # 设置舵机角度 servo_x.angle(angle_x, delay) servo_y.angle(angle_y, delay) pyb.delay(delay) # 定义矩形顶点 (像分辨率320x240) rect_points = [ (80, 60), # 左上 (240, 60), # 右上 (240, 180), # 右下 (80, 180) # 左下 ] while True: draw_rectangle(rect_points) ``` #### 3. **关键技术细节** 1. **防抖算法**: - 采用移动平均滤波处理坐标数据 ```python # 坐标滤波示例 history = [] def filtered_coord(x, y): history.append((x,y)) if len(history) > 5: history.pop(0) return [sum(v)//len(history) for v in zip(*history)] ``` 2. **激光干扰规避**: - 当检测到红色激光时,控制绿色激光保持1-2cm距离 - 使用距离判断算法: $$d = \sqrt{(x_r - x_g)^2 + (y_r - y_g)^2} > \text{threshold}$$ 3. **运动平滑处理**: - 贝塞尔曲线插值实现平滑转角 - 速度控制函数: $$v(t) = v_{max} \cdot (1 - e^{-t/\tau})$$ #### 4. **系统优化建议** 1. **双闭环控制**: ```mermaid graph LR A[位置指令] --> B[PID控制器] B --> C[舵机驱动] C --> D[云台位置] D --> E[OpenMV反馈] E --> B ``` 2. **硬件增强**: - 选用数字舵机(响应时间<0.1s) - 增加激光调制电路,避免环境光干扰 3. **标定流程**: 1. 将激光移至像四个角落 2. 记录对应的舵机PWM值 3. 求解线性映射矩阵: $$ \begin{bmatrix} \theta_h \\ \theta_v \end{bmatrix} = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} + \begin{bmatrix} b_1 \\ b_2 \end{bmatrix} $$ #### 5. **调试注意事项** - 舵机保护机制:设置PWM限幅(如660-840)[^2] - 电源隔离:舵机单独供电避免电压波动 - 实时反馈:通过串口输出调试数据 ```python print("Target: ({},{}) -> Angles: {},{}".format(x,y,angle_x,angle_y)) ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值