Skip to content

Commit 89b1577

Browse files
committed
* add block tracking demo
1 parent 08f6491 commit 89b1577

File tree

13 files changed

+1700
-0
lines changed

13 files changed

+1700
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
id: tracking_block
2+
name: tracking_block
3+
version: 1.0.0
4+
author: sipeed
5+
icon: ''
6+
desc: sipeed
7+
files:
8+
- assets/exit_touch.jpg
9+
- assets/exit.jpg
10+
- src/.trash/串口总线舵机管理器测试.ipynb
11+
- src/.trash/packet测试.ipynb
12+
- src/.trash/Python实验脚本-串口总线舵机通信.ipynb
13+
- src/__init__.py
14+
- src/data_table.py
15+
- src/packet_buffer.py
16+
- src/packet.py
17+
- src/uart_servo.py
18+
- main.py
19+
- servos.py
785 Bytes
Loading
6.5 KB
Loading
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import servos
2+
from collections import deque
3+
from maix import time, app, camera, image, display, touchscreen, nn, sys
4+
import cv2
5+
import numpy as np
6+
7+
# init uart register
8+
SERVO_PORT_NAME = '/dev/ttyS4' # 舵机串口号
9+
SERVO_BAUDRATE = 115200 # 舵机的波特率
10+
SERVO_ID = [1, 2] # 舵机ID,依次是pitch,roll,yaw
11+
SERVO_MIN_POSITION = [1650, 0] # 舵机位置最小值
12+
SERVO_MAX_POSITION = [2446, 4096] # 舵机位置最大值
13+
value_per_angle = 4096 / 270
14+
SERVO_ANGLE = [0, 0] # 舵机旋转的角度范围
15+
for i, _ in enumerate(SERVO_ANGLE):
16+
SERVO_ANGLE[i] = (SERVO_MAX_POSITION[i] - SERVO_MIN_POSITION[i]) / value_per_angle
17+
test_get_position_enable = False # 测试读取舵机的位置, 时能后舵机关闭扭矩, 并且持续读出舵机的位置
18+
test_set_pitch_position_enable = False # 测试设置pitch方向的位置
19+
test_set_roll_position_enable = False # 测试设置roll方向的位置
20+
21+
# OS04D10 fov h=90, v=81
22+
# SC850SL fov h=86.7, v=48.8
23+
CAMERA_FOV_H = 86.7 # 水平视场角, 查询镜头手册获取。
24+
CAMERA_FOV_V = 48.8 # 垂直视场角, 查询镜头手册获取。
25+
26+
# blobs_thresholds = [[0, 100, -31, -10, 83, 103]]
27+
# blobs_thresholds = [[0, 100, -31, 12, 77, 103]] # 小黄鸭 os04d10
28+
blobs_thresholds = [[0, 100, -13, 12, 50, 75]] # 小黄鸭 sc850sl
29+
# blobs_thresholds = [[25, 80, -39, -19, 21, 41]] # 绿色
30+
31+
blobs_area_threshold = 500 # blob最小面积
32+
blobs_pixels_threshold = 500 # blob最小像素点
33+
34+
pitch_pid = [0.45, 0.00001, 0.000, 0] # [P I D I_max]
35+
roll_pid = [0.6, 0.01, 0.000, 0] # [P I D I_max]
36+
37+
manual_wb_enable = True # 使能手动白平衡,保证找色块时颜色不会随色温变化
38+
wb_gain_list = [0.0682, 0, 0, 0.04897] # 手动白平衡增益参数
39+
40+
pitch_reverse = True # reverse out value direction
41+
roll_reverse = True # reverse out value direction
42+
43+
class RunningAverage:
44+
def __init__(self, n=5):
45+
self.n = n
46+
self.buf = deque(maxlen=n)
47+
def add(self, v):
48+
self.buf.append(v)
49+
def value(self):
50+
return sum(self.buf)/len(self.buf) if self.buf else 0.0
51+
52+
class Status:
53+
GET_RETANGLE = 0,
54+
DETECT = 1,
55+
56+
def rgb_list_to_lab_list(rgb_list):
57+
"""
58+
将RGB列表转换为Lab列表
59+
rgb_list: [[r, g, b], [r, g, b], ...],值范围0-255
60+
返回: [[L, a, b], [L, a, b], ...]
61+
"""
62+
# 转换为numpy数组
63+
rgb_array = np.array(rgb_list, dtype=np.uint8).reshape(-1, 1, 3)
64+
65+
# 转换为float并归一化
66+
rgb_float = rgb_array.astype(np.float32) / 255.0
67+
68+
# 转换到Lab颜色空间
69+
lab_array = cv2.cvtColor(rgb_float, cv2.COLOR_RGB2LAB)
70+
71+
# 转换回列表格式
72+
lab_list = lab_array.reshape(-1, 3).astype(int).tolist()
73+
74+
return lab_list
75+
76+
class App:
77+
def __init__(self, gimbal: servos.Gimbal):
78+
self.cam = camera.Camera(640, 360)
79+
self.disp = display.Display()
80+
self.ts = touchscreen.TouchScreen()
81+
self.img_exit = image.load("./assets/exit.jpg").resize(40, 40)
82+
self.img_exit_touch = image.load("./assets/exit_touch.jpg").resize(40, 40)
83+
self.exit_box = [0, 0, self.img_exit.width(), self.img_exit.height()]
84+
self.need_exit = False
85+
86+
if manual_wb_enable:
87+
self.cam.awb_mode(camera.AwbMode.Manual)
88+
self.cam.set_wb_gain(wb_gain_list)
89+
self.gimbal = gimbal
90+
self.smooth_pitch = RunningAverage()
91+
self.smooth_roll = RunningAverage()
92+
self.status = Status.GET_RETANGLE
93+
94+
def check_touch_box(self, t, box, oft = 0):
95+
"""This method is used for exiting and you normally do not need to modify or call it.
96+
You usually don't need to modify it.
97+
"""
98+
if t[2] and t[0] + oft > box[0] and t[0] < box[0] + box[2] + oft and t[1] + oft > box[1] and t[1] < box[1] + box[3] + oft:
99+
return True
100+
else:
101+
return False
102+
103+
def run(self):
104+
pressing = False
105+
target = nn.Object()
106+
btn_str = "Select"
107+
font_size = image.string_size(btn_str)
108+
109+
while not app.need_exit():
110+
if self.status == Status.GET_RETANGLE:
111+
img = self.cam.read()
112+
touch_status = self.ts.read()
113+
if touch_status[2]: # Finger press detected
114+
if not pressing:
115+
target.x = touch_status[0]
116+
target.y = touch_status[1]
117+
print("Start select")
118+
pressing = True
119+
else:
120+
if pressing: # Finger released, finalize selection
121+
target.w = touch_status[0] - target.x
122+
target.h = touch_status[1] - target.y
123+
if target.w > 0 and target.h > 0:
124+
print(f"Init with rectangle x: {target.x}, y: {target.y}, w: {target.w}, h: {target.h}")
125+
rgb_list = img.get_pixel(target.x + target.w // 2, target.y + target.h // 2, True)
126+
print('rgb list', rgb_list)
127+
lab_list = rgb_list_to_lab_list(rgb_list)[0]
128+
print('lab list', lab_list)
129+
blobs_thresholds = [[lab_list[0] - 15, lab_list[0] + 15, lab_list[1] - 15, lab_list[1] + 15, lab_list[2] - 15, lab_list[2] + 15]]
130+
print('blobs_thresholds', blobs_thresholds)
131+
self.status = Status.DETECT
132+
else:
133+
print(f"Rectangle invalid, x: {target.x}, y: {target.y}, w: {target.w}, h: {target.h}")
134+
pressing = False
135+
if pressing:
136+
img.draw_string(2, img.height() - font_size[1] * 2, "Select and release to complete", image.Color.from_rgb(255, 0, 0), 1.5)
137+
img.draw_rect(target.x, target.y, touch_status[0] - target.x, touch_status[1] - target.y, image.Color.from_rgb(255, 0, 0), 3)
138+
else:
139+
img.draw_string(2, img.height() - font_size[1] * 2, "Select target on screen", image.Color.from_rgb(255, 0, 0), 1.5)
140+
self.disp.show(img)
141+
elif self.status == Status.DETECT:
142+
pitch_error = 0
143+
roll_error = 0
144+
img = self.cam.read() # Reads an image frame.
145+
touch_status = self.ts.read()
146+
if touch_status[2]:
147+
self.status = Status.GET_RETANGLE
148+
img_w = img.width()
149+
img_h = img.height()
150+
obj_x = -1
151+
obj_y = -1
152+
blobs = img.find_blobs(blobs_thresholds, area_threshold = blobs_area_threshold, pixels_threshold = blobs_pixels_threshold)
153+
if len(blobs) > 0:
154+
b = blobs[0]
155+
corners = b.mini_corners()
156+
obj_x = b.cx()
157+
obj_y = b.cy()
158+
159+
print(b.cx(), b.cy())
160+
for i in range(4):
161+
img.draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image.COLOR_RED)
162+
163+
if obj_x != -1 and obj_y != -1:
164+
center_x = img_w / 2
165+
center_y = img_h / 2
166+
angle_x = (obj_x - center_x) / img_w * CAMERA_FOV_H
167+
angle_y = (obj_y - center_y) / img_h * CAMERA_FOV_V
168+
print(f'angle_x:{angle_x:.2f} fov_h:{CAMERA_FOV_H:.2f}', f'angle_y:{angle_y:.2f} fov_v:{CAMERA_FOV_V:.2f}')
169+
170+
# # 偏移的角度映射到舵机偏移的百分比
171+
# pitch_error = (angle_y + SERVO_ANGLE[0] / 2) / SERVO_ANGLE[0] * 100
172+
# roll_error = (angle_x + SERVO_ANGLE[1] / 2) / SERVO_ANGLE[1] * 100
173+
174+
# 偏移的角度转舵机旋转值
175+
pitch_error = angle_y * value_per_angle
176+
roll_error = angle_x * value_per_angle
177+
178+
print(f'pitch_error:{pitch_error:.2f} pitch_angle_range:{SERVO_ANGLE[0]:.2f} roll_error:{roll_error:.2f} roll_angle_range:{SERVO_ANGLE[1]:.2f}')
179+
gimbal.run(pitch_error, roll_error, pitch_reverse = pitch_reverse, roll_reverse=roll_reverse)
180+
181+
if self.need_exit:
182+
app.set_exit_flag(True)
183+
break
184+
185+
t = self.ts.read()
186+
img = img.resize(self.disp.width(), self.disp.height(), image.Fit.FIT_CONTAIN)
187+
if self.check_touch_box(t, self.exit_box, 20):
188+
img.draw_image(self.exit_box[0], self.exit_box[1], self.img_exit_touch)
189+
self.need_exit = True
190+
else:
191+
img.draw_image(self.exit_box[0], self.exit_box[1], self.img_exit)
192+
193+
self.disp.show(img)
194+
if __name__ == '__main__':
195+
s = servos.Servos(SERVO_ID, SERVO_PORT_NAME, SERVO_BAUDRATE, SERVO_MIN_POSITION, SERVO_MAX_POSITION)
196+
pid_pitch = servos.PID(p=pitch_pid[0], i=pitch_pid[1], d=pitch_pid[2], imax=pitch_pid[3])
197+
pid_roll = servos.PID(p=roll_pid[0], i=roll_pid[1], d=roll_pid[2], imax=roll_pid[3])
198+
gimbal = servos.Gimbal(s, pid_pitch, pid_roll)
199+
200+
if test_get_position_enable:
201+
s.test_position()
202+
elif test_set_pitch_position_enable:
203+
s.uservo.set_position(1, 2048)
204+
while True:
205+
pos = s.uservo.get_position(1)
206+
if pos:
207+
if pos > 2000 and pos < 3100:
208+
break
209+
time.sleep_ms(1)
210+
211+
t = time.ticks_us()
212+
s.uservo.set_position(1, 2548)
213+
while True:
214+
pos = s.uservo.get_position(1)
215+
if pos:
216+
if pos > 2548-50 and pos < 2548+50:
217+
break
218+
time.sleep_ms(1)
219+
print('cost', time.ticks_us() - t)
220+
elif test_set_roll_position_enable:
221+
id = 2
222+
angle = 120
223+
start_position = 1024
224+
dst_position = start_position + angle / 270 * 4096
225+
s.uservo.set_position(id, start_position)
226+
227+
while True:
228+
pos = s.uservo.get_position(id)
229+
if pos:
230+
if pos > start_position - 25 and pos < start_position + 25:
231+
break
232+
print('wait start position, dst position', dst_position,'curr position', pos)
233+
time.sleep_ms(100)
234+
235+
t = time.ticks_us()
236+
s.uservo.set_position(id, dst_position)
237+
print('run set_position cost', time.ticks_us() - t)
238+
239+
t = time.ticks_us()
240+
while True:
241+
pos = s.uservo.get_position(id)
242+
if pos:
243+
if pos > dst_position-25 and pos < dst_position+100:
244+
break
245+
print('wait dst position, dst position', dst_position,'curr position', pos)
246+
# time.sleep_ms(1)
247+
print('wait cost', time.ticks_us() - t)
248+
249+
while not app.need_exit():
250+
pos = s.uservo.get_position(id)
251+
print(pos)
252+
time.sleep_ms(1000)
253+
else:
254+
a = App(gimbal)
255+
a.run()

0 commit comments

Comments
 (0)