Skip to content

Commit a07a744

Browse files
committed
Initial commit
0 parents  commit a07a744

7 files changed

Lines changed: 570 additions & 0 deletions

File tree

.DS_Store

6 KB
Binary file not shown.

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

OminiBot_HV.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import serial
2+
import struct
3+
import time
4+
5+
class ominibothv:
6+
def __init__(self,
7+
port = '/dev/ominibot',
8+
baud = 115200,
9+
divisor_mode = 3,
10+
motor_direct = 2,
11+
encoder_direct = 0,
12+
motor_pwm_max = 5200,
13+
motor_pwm_min = 720,
14+
encoder_ppr = 390,
15+
wheel_space = 110,
16+
axle_space = 0,
17+
gear_ratio = 30,
18+
wheel_diameter = 60,
19+
pos_kp = 3000,
20+
pos_ki = 1050,
21+
pos_kd = 0,
22+
vel_kp = 3000,
23+
vel_ki = 1050):
24+
25+
self.ser = serial.Serial(port, baud, timeout = 1)
26+
27+
self.robot_mode = divisor_mode
28+
29+
self.forced_stop()
30+
time.sleep(0.5)
31+
32+
#system setting(motor range: 3v-6v, encoder ppr: 660/4 = 165)
33+
sys_set = bytearray(b'\x7b\x23')
34+
sys_set += motor_direct.to_bytes(1, byteorder='big')
35+
sys_set += encoder_direct.to_bytes(1, byteorder='big')
36+
sys_set += motor_pwm_max.to_bytes(2, byteorder='big')
37+
sys_set += motor_pwm_min.to_bytes(2, byteorder='big')
38+
sys_set += encoder_ppr.to_bytes(2, byteorder='big')
39+
sys_set += bytearray(b'\x00\x00')
40+
bcc = self.calculate_bcc(sys_set).to_bytes(1, byteorder='big')
41+
sys_set += bcc + bytearray(b'\x7d')
42+
self.ser.write(sys_set)
43+
time.sleep(0.1)
44+
45+
# robot size setting(motor dear 1:55)
46+
bot_set = bytearray(b'\x7b\x24')
47+
bot_set += wheel_space.to_bytes(2, byteorder='big')
48+
bot_set += axle_space.to_bytes(2, byteorder='big')
49+
bot_set += gear_ratio.to_bytes(2, byteorder='big')
50+
bot_set += wheel_diameter.to_bytes(2, byteorder='big')
51+
bot_set += bytearray(b'\x00\x00')
52+
bcc = self.calculate_bcc(bot_set).to_bytes(1, byteorder='big')
53+
bot_set += bcc + bytearray(b'\x7d')
54+
self.ser.write(bot_set)
55+
time.sleep(0.1)
56+
57+
# robot pid setting
58+
pid_set = bytearray(b'\x7b\x40')
59+
pid_set += pos_kp.to_bytes(2, byteorder='big')
60+
pid_set += pos_ki.to_bytes(2, byteorder='big')
61+
pid_set += pos_kd.to_bytes(2, byteorder='big')
62+
pid_set += vel_kp.to_bytes(2, byteorder='big')
63+
pid_set += vel_ki.to_bytes(2, byteorder='big')
64+
bcc = self.calculate_bcc(pid_set).to_bytes(1, byteorder='big')
65+
pid_set += bcc + bytearray(b'\x7d')
66+
self.ser.write(pid_set)
67+
time.sleep(0.1)
68+
69+
def check_cmd(self, name):
70+
if name == 'system':
71+
self.ser.write(b'\x7b\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x7d')
72+
elif name == 'robot':
73+
self.ser.write(b'\x7b\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4f\x7d')
74+
else:
75+
self.ser.write(b'\x7b\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2b\x7d')
76+
77+
read_val = self.ser.read(14)
78+
79+
return read_val
80+
81+
def calculate_bcc(self, data):
82+
bcc = 0
83+
for byte in data:
84+
bcc ^= byte
85+
return bcc
86+
87+
def clamp_number(self, num , a, b):
88+
return max(min(num, max(a, b)), min(a, b))
89+
90+
def forced_stop(self):
91+
set_motor_go = bytearray(b'\x7b\x25\x00')
92+
set_motor_go += self.robot_mode.to_bytes(1, byteorder='big')
93+
set_motor_go += bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00')
94+
bcc = self.calculate_bcc(set_motor_go).to_bytes(1, byteorder='big')
95+
set_motor_go += bcc + bytearray(b'\x7d')
96+
self.ser.write(set_motor_go)
97+
98+
def motor_speed(self, m1, m2, m3, m4):
99+
set_motor_go = bytearray(b'\x7b\x26\x02')
100+
set_motor_go += self.robot_mode.to_bytes(1, byteorder='big')
101+
set_motor_go += struct.pack('!i',int(m1*1000))[2:]
102+
set_motor_go += struct.pack('!i',int(m2*1000))[2:]
103+
set_motor_go += struct.pack('!i',int(m3*1000))[2:]
104+
set_motor_go += struct.pack('!i',int(m4*1000))[2:]
105+
106+
bcc = self.calculate_bcc(set_motor_go).to_bytes(1, byteorder='big')
107+
108+
set_motor_go += bcc + bytearray(b'\x7d')
109+
110+
self.ser.write(set_motor_go)
111+
112+
def robot_speed(self, lx, ly, az):
113+
set_motor_go = bytearray(b'\x7b\x25\x02')
114+
set_motor_go += self.robot_mode.to_bytes(1, byteorder='big')
115+
set_motor_go += struct.pack('!i',int(lx*1000))[2:]
116+
set_motor_go += struct.pack('!i',int(ly*1000))[2:]
117+
set_motor_go += struct.pack('!i',int(az*1000))[2:]
118+
set_motor_go += bytearray(b'\x00\x00')
119+
120+
bcc = self.calculate_bcc(set_motor_go).to_bytes(1, byteorder='big')
121+
122+
set_motor_go += bcc + bytearray(b'\x7d')
123+
self.ser.write(set_motor_go)
124+
125+
126+
def serial_close(self):
127+
self.ser.close()
128+
129+
def serial_write(self, cmd):
130+
self.ser.write(cmd)
131+
132+
def serial_read(self, choose=None):
133+
self.ser.read(choose)
134+
135+
def read_robot_data(self):
136+
while True:
137+
if self.ser.read().hex() == '7b':
138+
robot_vel = self.ser.read(7)[1:]
139+
imu_val = self.ser.read(20)
140+
bat_val = self.ser.read(3)
141+
if self.ser.read(1).hex() == '7d':
142+
check_code = bytearray(b'\x7b\x00') + robot_vel + imu_val + bat_val[:2]
143+
bcc = self.calculate_bcc(check_code)
144+
if hex(bcc) == hex(bat_val[2]):
145+
check = 1
146+
else:
147+
check = 0
148+
break
149+
150+
return check, robot_vel, imu_val
151+
152+
import numpy as np
153+
154+
def quaternion_to_euler(q):
155+
# 歸一化四元數
156+
q = q / np.linalg.norm(q)
157+
158+
# 計算歐拉角
159+
roll = np.arctan2(2*(q[0]*q[1] + q[2]*q[3]), 1 - 2*(q[1]**2 + q[2]**2))
160+
pitch = np.arcsin(2*(q[0]*q[2] - q[3]*q[1]))
161+
yaw = np.arctan2(2*(q[0]*q[3] + q[1]*q[2]), 1 - 2*(q[2]**2 + q[3]**2))
162+
163+
# 將歐拉角從弧度轉換為角度
164+
roll = np.degrees(roll)
165+
pitch = np.degrees(pitch)
166+
yaw = np.degrees(yaw)
167+
168+
return roll, pitch, yaw
169+
170+
if __name__ == '__main__':
171+
import time
172+
pi = ominibothv(
173+
port = '/dev/ominibot',
174+
baud = 115200,
175+
divisor_mode = 3)
176+
177+
pi.robot_speed(0.2, 0.0, 0.0)
178+
time.sleep(10)
179+
pi.robot_speed(0.0, 0.0, 0.0)
180+
181+
182+

image_save.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import cv2
2+
import uuid
3+
cap = cv2.VideoCapture(0)
4+
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G'))
5+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
6+
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
7+
8+
while True:
9+
ret, frame = cap.read()
10+
cv2.imshow('frame', frame)
11+
12+
13+
keyin = cv2.waitKey(1) & 0xFF
14+
15+
if keyin == ord('q'):
16+
break
17+
elif keyin == ord('s'):
18+
cv2.imwrite('{}.jpg'.format(uuid.uuid1()), frame)
19+
20+
cap.release()
21+
cv2.destroyAllWindows()

lane_detection.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import cv2
2+
import numpy as np
3+
import time
4+
from OminiBot_HV import ominibothv
5+
6+
def region_of_interest(img, vertices):
7+
mask = np.zeros_like(img)
8+
cv2.fillPoly(mask, vertices, (255, 255, 255))
9+
return cv2.bitwise_and(img, mask)
10+
11+
# hsv color
12+
low_yellow = np.array([26, 77, 100])
13+
high_yellow = np.array([34, 255, 255])
14+
low_white = np.array([0, 0, 221])
15+
high_white = np.array([180, 15, 255])
16+
17+
cap = cv2.VideoCapture(0)
18+
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
19+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
20+
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
21+
22+
robot_control = ominibothv('/dev/ominibot', 115200)
23+
time.sleep(3)
24+
25+
CENTER_X = 160
26+
BASE_SPEED = 0.15
27+
TURN_GAIN = BASE_SPEED / 80.0
28+
SMOOTH = 0.5
29+
HALF_LANE = 70 # px offset when following a single wall
30+
MARGIN = 20 # dead zone around center for left/right classing
31+
Y0, Y1 = 145, 205 # lookahead scan band
32+
CORR_MAX = 0.16
33+
MAX_STEP = 35 # max target move per frame (anti-runaway / anti-jump)
34+
LOST_STOP = 40
35+
MIN_AREA = 25 # ignore tiny noise blobs
36+
37+
interest_vertices = [np.array([
38+
[0, 240], [0, 200], [80, 120], [240, 120], [320, 200], [320, 240]
39+
])]
40+
41+
def blob_centroids(band):
42+
num, _, stats, cents = cv2.connectedComponentsWithStats(band, connectivity=8)
43+
return [cents[i][0] for i in range(1, num) if stats[i, cv2.CC_STAT_AREA] >= MIN_AREA]
44+
45+
show_ok = True
46+
prev_correction = 0.0
47+
prev_target = CENTER_X
48+
lost = 0
49+
50+
try:
51+
while True:
52+
ret, frame = cap.read()
53+
if not ret or frame is None:
54+
robot_control.motor_speed(0.0, 0.0, 0.0, 0.0)
55+
continue
56+
57+
cropped = region_of_interest(frame.copy(), interest_vertices)
58+
hsv = cv2.cvtColor(cropped, cv2.COLOR_BGR2HSV)
59+
yellow_mask = cv2.inRange(hsv, low_yellow, high_yellow)
60+
white_mask = cv2.inRange(hsv, low_white, high_white)
61+
62+
# ---- yellow walls: classify blobs by side, take the inner one each side
63+
yblobs = blob_centroids(yellow_mask[Y0:Y1, :])
64+
left_side = [x for x in yblobs if x < CENTER_X - MARGIN]
65+
right_side = [x for x in yblobs if x > CENTER_X + MARGIN]
66+
left_wall = max(left_side) if left_side else None # rightmost left blob
67+
right_wall = min(right_side) if right_side else None # leftmost right blob
68+
69+
# ---- white center line: the white blob nearest to image center
70+
wblobs = blob_centroids(white_mask[Y0:Y1, :])
71+
white_x = min(wblobs, key=lambda x: abs(x - CENTER_X)) if wblobs else None
72+
73+
# ---- choose target ----
74+
if left_wall is not None and right_wall is not None:
75+
target_x = int((left_wall + right_wall) / 2)
76+
src = "walls"
77+
elif white_x is not None:
78+
target_x = int(white_x)
79+
src = "white"
80+
elif left_wall is not None:
81+
target_x = int(left_wall + HALF_LANE)
82+
src = "Lwall"
83+
elif right_wall is not None:
84+
target_x = int(right_wall - HALF_LANE)
85+
src = "Rwall"
86+
else:
87+
target_x = prev_target
88+
src = "hold"
89+
90+
have_signal = (left_wall is not None) or (right_wall is not None) or (white_x is not None)
91+
lost = 0 if have_signal else lost + 1
92+
93+
# rate limit: target can only move so far per frame
94+
target_x = max(prev_target - MAX_STEP, min(prev_target + MAX_STEP, target_x))
95+
target_x = max(0, min(319, target_x))
96+
prev_target = target_x
97+
target_y = (Y0 + Y1) // 2
98+
99+
# ---- steering ----
100+
error = target_x - CENTER_X
101+
correction = error * TURN_GAIN
102+
correction = SMOOTH * correction + (1 - SMOOTH) * prev_correction
103+
correction = max(min(correction, CORR_MAX), -CORR_MAX)
104+
prev_correction = correction
105+
106+
forward = max(0.07, BASE_SPEED - 0.6 * abs(correction))
107+
if lost > LOST_STOP:
108+
forward = 0.0
109+
correction = 0.0
110+
src = "STOP"
111+
112+
robot_speed_l = max(min(forward + correction, 0.30), -0.12)
113+
robot_speed_r = max(min(forward - correction, 0.30), -0.12)
114+
115+
print("{:>5} L:{} R:{} w:{} tgt:{:>3} corr:{:+.3f} l:{:.2f} r:{:.2f}".format(
116+
src, None if left_wall is None else int(left_wall),
117+
None if right_wall is None else int(right_wall),
118+
white_x if white_x is None else int(white_x),
119+
target_x, correction, robot_speed_l, robot_speed_r))
120+
121+
robot_control.motor_speed(robot_speed_l * -1, robot_speed_r, 0.0, 0.0)
122+
123+
# ---- visualization ----
124+
cv2.rectangle(frame, (0, Y0), (319, Y1), (80, 80, 80), 1)
125+
if left_wall is not None:
126+
cv2.circle(frame, (int(left_wall), target_y), 6, (255, 0, 0), -1)
127+
if right_wall is not None:
128+
cv2.circle(frame, (int(right_wall), target_y), 6, (255, 0, 0), -1)
129+
if white_x is not None:
130+
cv2.circle(frame, (int(white_x), target_y), 6, (0, 255, 0), -1)
131+
cv2.line(frame, (CENTER_X, 0), (CENTER_X, 240), (0, 0, 255), 1)
132+
cv2.arrowedLine(frame, (CENTER_X, 240), (target_x, target_y), (0, 255, 255), 3, tipLength=0.3)
133+
cv2.circle(frame, (target_x, target_y), 6, (255, 0, 255), -1)
134+
cv2.imwrite('debug_live.jpg', frame)
135+
if show_ok:
136+
try:
137+
cv2.imshow('frame', frame)
138+
if (cv2.waitKey(1) & 0xFF) == ord('q'):
139+
break
140+
except cv2.error:
141+
show_ok = False
142+
143+
except KeyboardInterrupt:
144+
pass
145+
finally:
146+
robot_control.motor_speed(0.0, 0.0, 0.0, 0.0)
147+
cap.release()
148+
cv2.destroyAllWindows()

0 commit comments

Comments
 (0)