优化控制界面UI样式,增加外界手柄控制

This commit is contained in:
张梦南 2026-04-18 16:46:34 +08:00
parent 7fbe4f59cd
commit bd13a5aeb2
10 changed files with 779 additions and 79 deletions

View File

@ -6,14 +6,16 @@
justify-content: center;
min-height: 80vh;
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.title {
font-size: 24px;
font-size: 28px;
font-weight: bold;
color: #333;
margin-bottom: 40px;
text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
/* 手柄风格布局 */
@ -23,46 +25,66 @@
align-items: center;
gap: 30px;
width: 100%;
max-width: 350px;
max-width: 600px;
}
/* 移动控制按钮区域 */
.movement-controls {
/* 控制器布局 */
.controller-layout {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
padding: 30px;
background: linear-gradient(145deg, #e6e6e6, #ffffff);
border-radius: 20px;
box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.1), -10px -10px 20px rgba(255, 255, 255, 0.7);
}
/* 方向键区域 */
.dpad {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 100%;
gap: 10px;
}
.movement-row {
.dpad-row {
display: flex;
gap: 15px;
justify-content: center;
gap: 10px;
}
/* 录制回放按钮区域 */
.record-controls {
/* 中间录制回放控制 */
.center-controls {
display: flex;
flex-direction: column;
gap: 15px;
justify-content: center;
width: 100%;
margin-top: 20px;
align-items: center;
}
/* 右侧旋转控制 */
.rotate-controls {
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
}
/* 按钮样式 */
.btn {
width: 120px;
height: 50px;
width: 70px;
height: 70px;
border: none;
border-radius: 25px;
font-size: 16px;
border-radius: 15px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
}
.btn::before {
@ -81,93 +103,168 @@
z-index: 2;
}
/* 方向按钮样式 */
.btn-forward {
background-color: #4CAF50;
background: linear-gradient(145deg, #4CAF50, #45a049);
color: white;
}
.btn-backward {
background-color: #f44336;
background: linear-gradient(145deg, #f44336, #da190b);
color: white;
}
.btn-left {
background-color: #2196F3;
background: linear-gradient(145deg, #2196F3, #1976D2);
color: white;
}
.btn-right {
background-color: #FF9800;
color: white;
}
.btn-rotate-left {
background-color: #9C27B0;
color: white;
}
.btn-rotate-right {
background-color: #607D8B;
background: linear-gradient(145deg, #FF9800, #F57C00);
color: white;
}
/* 斜向按钮样式 */
.btn-forward-left {
background-color: #4CAF50;
background: linear-gradient(145deg, #4CAF50, #45a049);
color: white;
}
.btn-forward-right {
background-color: #4CAF50;
background: linear-gradient(145deg, #4CAF50, #45a049);
color: white;
}
.btn-backward-left {
background-color: #f44336;
background: linear-gradient(145deg, #f44336, #da190b);
color: white;
}
.btn-backward-right {
background-color: #f44336;
background: linear-gradient(145deg, #f44336, #da190b);
color: white;
}
/* 旋转按钮样式 */
.btn-rotate-left {
background: linear-gradient(145deg, #9C27B0, #7B1FA2);
color: white;
}
.btn-rotate-right {
background: linear-gradient(145deg, #607D8B, #455A64);
color: white;
}
/* 中心方块样式 */
.btn-center {
background: linear-gradient(145deg, #666666, #444444);
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7);
cursor: default;
}
.btn-center:hover {
transform: none;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7);
}
.btn-center:active {
transform: none;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1), -5px -5px 10px rgba(255, 255, 255, 0.7);
}
/* 录制回放按钮样式 */
.btn-record {
background: linear-gradient(145deg, #FF5722, #E64A19);
color: white;
width: 120px;
height: 50px;
font-size: 16px;
border-radius: 25px;
}
.btn-playback {
background: linear-gradient(145deg, #00BCD4, #0097A7);
color: white;
width: 120px;
height: 50px;
font-size: 16px;
border-radius: 25px;
}
/* 按钮动画效果 */
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.15), -8px -8px 16px rgba(255, 255, 255, 0.8);
}
.btn:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2), -3px -3px 6px rgba(255, 255, 255, 0.6);
}
/* 状态显示 */
.status {
margin-top: 30px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 10px;
min-height: 50px;
padding: 20px;
background: linear-gradient(145deg, #f5f5f5, #e0e0e0);
border-radius: 15px;
min-height: 60px;
width: 100%;
max-width: 350px;
max-width: 400px;
text-align: center;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1), inset -5px -5px 10px rgba(255, 255, 255, 0.7);
}
.status p {
color: #666;
margin: 0;
font-size: 16px;
font-size: 18px;
font-weight: 500;
}
/* 响应式设计 */
@media (max-width: 400px) {
@media (max-width: 650px) {
.controller-layout {
flex-direction: column;
gap: 20px;
padding: 20px;
}
.dpad-row {
gap: 8px;
}
.btn {
width: 60px;
height: 60px;
font-size: 18px;
}
.btn-record, .btn-playback {
width: 100px;
height: 45px;
font-size: 14px;
}
.title {
font-size: 24px;
}
}
@media (max-width: 400px) {
.btn {
width: 50px;
height: 50px;
font-size: 16px;
}
.btn-record, .btn-playback {
width: 90px;
height: 40px;
font-size: 13px;
}
.title {
font-size: 20px;
}

View File

@ -56,6 +56,16 @@ h1 {
color: white;
}
.btn-record {
background-color: #ff9800;
color: white;
}
.btn-playback {
background-color: #2196f3;
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);

View File

@ -424,6 +424,8 @@ function stopMotor() {
const timestamp = Date.now() - window.recordingStartTime;
window.recordedActions.push({ direction: 'stop', timestamp });
console.log('Recorded action:', { direction: 'stop', timestamp });
} else {
console.log('Not recording, action:', 'stop');
}
// 发送停止请求到服务器
@ -437,7 +439,8 @@ function stopMotor() {
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// 停止成功,不显示消息
// 停止成功,显示已停止消息
status.innerHTML = `<p>${data.message}</p>`;
} else {
status.innerHTML = `<p>错误: ${data.message}</p>`;
}

247
main/Javascript/joycon.js Normal file
View File

@ -0,0 +1,247 @@
// 确保在DOM加载完成后执行
window.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded - Joycon.js executing');
// 游戏手柄相关变量
let gamepad = null;
let lastDirection = 'stop';
let isRecording = false;
let isPlaying = false;
let recordedActions = [];
let recordingStartTime = 0;
// 按键状态标志(用于检测按键按下事件)
let recordButtonPressed = false;
let playbackButtonPressed = false;
// 获取状态元素
const status = document.getElementById('status');
// 检查浏览器是否支持游戏手柄API
if (!('getGamepads' in navigator)) {
status.innerHTML = '<p>您的浏览器不支持游戏手柄API</p>';
console.log('Gamepad API not supported');
return;
}
// 连接游戏手柄
window.addEventListener('gamepadconnected', function(e) {
gamepad = e.gamepad;
status.innerHTML = `<p>已连接游戏手柄: ${gamepad.id}</p>`;
console.log('Gamepad connected:', gamepad);
// 开始监听游戏手柄输入
requestAnimationFrame(updateGamepad);
});
// 断开游戏手柄
window.addEventListener('gamepaddisconnected', function(e) {
status.innerHTML = '<p>游戏手柄已断开连接</p>';
console.log('Gamepad disconnected:', e.gamepad);
gamepad = null;
});
// 游戏手柄输入更新函数
function updateGamepad() {
if (!gamepad) {
requestAnimationFrame(updateGamepad);
return;
}
// 获取游戏手柄状态
const gamepads = navigator.getGamepads();
gamepad = gamepads[gamepad.index];
if (!gamepad) {
requestAnimationFrame(updateGamepad);
return;
}
// 处理摇杆输入 (通常是0号摇杆左摇杆)
const leftStickX = gamepad.axes[0];
const leftStickY = gamepad.axes[1];
// 处理按键输入
const buttons = gamepad.buttons;
// 摇杆死区
const deadzone = 0.3;
// 确定移动方向
let direction = 'stop';
if (Math.abs(leftStickX) > deadzone || Math.abs(leftStickY) > deadzone) {
// 计算角度
const angle = Math.atan2(leftStickY, leftStickX) * 180 / Math.PI;
// 根据角度确定方向(修复:交换前后方向,修复:使用下划线分隔的方向值)
if (angle >= -22.5 && angle < 22.5) {
direction = 'right'; // 右
} else if (angle >= 22.5 && angle < 67.5) {
direction = 'backward_right'; // 右后
} else if (angle >= 67.5 && angle < 112.5) {
direction = 'backward'; // 后
} else if (angle >= 112.5 && angle < 157.5) {
direction = 'backward_left'; // 左后
} else if (angle >= 157.5 || angle < -157.5) {
direction = 'left'; // 左
} else if (angle >= -157.5 && angle < -112.5) {
direction = 'forward_left'; // 左前
} else if (angle >= -112.5 && angle < -67.5) {
direction = 'forward'; // 前
} else if (angle >= -67.5 && angle < -22.5) {
direction = 'forward_right'; // 右前
}
}
// 处理旋转按钮 (X键和Y键)
if (buttons[2].pressed) { // X键 - 左旋转
direction = 'rotate_left';
} else if (buttons[3].pressed) { // Y键 - 右旋转
direction = 'rotate_right';
}
// 处理录制按钮 (十字键上键) - 修复:检测按键按下事件
if (buttons[12].pressed) { // 十字键上键 - 录制
if (!recordButtonPressed) {
recordButtonPressed = true;
if (!isPlaying) {
if (isRecording) {
// 停止录制
isRecording = false;
status.innerHTML = `<p>录制完成,共记录 ${recordedActions.length} 个操作</p>`;
console.log('Recording stopped, actions:', recordedActions);
} else {
// 开始录制
isRecording = true;
recordedActions = [];
recordingStartTime = Date.now();
status.innerHTML = '<p>开始录制...</p>';
console.log('Recording started');
}
}
}
} else {
recordButtonPressed = false;
}
// 处理回放按钮 (十字键左键) - 修复:检测按键按下事件
if (buttons[14].pressed) { // 十字键左键 - 回放
if (!playbackButtonPressed) {
playbackButtonPressed = true;
if (!isRecording && !isPlaying && recordedActions.length > 0) {
isPlaying = true;
status.innerHTML = '<p>开始回放...</p>';
console.log('Starting playback');
// 按时间顺序回放操作
let currentTime = 0;
const totalDuration = recordedActions[recordedActions.length - 1].timestamp;
recordedActions.forEach((action, index) => {
if (index === 0) {
// 第一个操作立即执行
console.log('Executing action immediately:', action);
controlMotor(action.direction);
} else {
// 计算与前一个操作的时间差
const prevAction = recordedActions[index - 1];
const delay = action.timestamp - prevAction.timestamp;
currentTime += delay;
console.log('Scheduling action:', action, 'at delay:', currentTime);
setTimeout(() => {
console.log('Executing action:', action);
controlMotor(action.direction);
}, currentTime);
}
});
// 回放完成后,确保发送停止请求
setTimeout(() => {
console.log('Playback completed, sending stop command');
controlMotor('stop');
status.innerHTML = '<p>回放完成</p>';
console.log('Playback completed');
isPlaying = false;
}, totalDuration + 2000);
}
}
} else {
playbackButtonPressed = false;
}
// 如果方向改变,发送控制命令
if (direction !== lastDirection) {
// 如果正在录制,记录操作
if (isRecording) {
const action = {
direction: direction,
timestamp: Date.now() - recordingStartTime
};
recordedActions.push(action);
}
controlMotor(direction);
lastDirection = direction;
}
// 继续监听
requestAnimationFrame(updateGamepad);
}
// 控制电机的函数与control.js中的相同
function controlMotor(direction) {
// 根据方向设置状态消息
if (direction === 'stop') {
status.innerHTML = `<p>正在停止...</p>`;
} else {
let actionText = '';
switch (direction) {
case 'forward': actionText = '前进'; break;
case 'backward': actionText = '后退'; break;
case 'left': actionText = '左移'; break;
case 'right': actionText = '右移'; break;
case 'rotate_left': actionText = '左旋转'; break;
case 'rotate_right': actionText = '右旋转'; break;
case 'forward_left': actionText = '左前移动'; break;
case 'forward_right': actionText = '右前移动'; break;
case 'backward_left': actionText = '左后移动'; break;
case 'backward_right': actionText = '右后移动'; break;
default: actionText = '移动'; break;
}
status.innerHTML = `<p>正在${actionText}...</p>`;
}
// 记录操作(如果正在录制)
if (isRecording) {
const timestamp = Date.now() - recordingStartTime;
// 注意这里不再重复记录因为已经在updateGamepad中记录了
console.log('Recorded action:', { direction, timestamp });
} else {
console.log('Not recording, action:', direction);
}
// 发送AJAX请求到后端服务器
fetch('/control', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ direction: direction })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
status.innerHTML = `<p>${data.message}</p>`;
} else {
status.innerHTML = `<p>错误: ${data.message}</p>`;
}
})
.catch(error => {
status.innerHTML = `<p>通信错误: ${error.message}</p>`;
});
}
// 初始化
status.innerHTML = '<p>请连接游戏手柄...</p>';
console.log('Joycon.js initialized');
});

View File

@ -1,12 +1,25 @@
// 获取按钮和状态元素
const forwardBtn = document.getElementById('forwardBtn');
const backwardBtn = document.getElementById('backwardBtn');
const recordBtn = document.getElementById('recordBtn');
const playbackBtn = document.getElementById('playbackBtn');
const status = document.getElementById('status');
// 录制相关变量
let isRecording = false;
let recordedActions = [];
let recordingStartTime = 0;
// 实际控制函数
function controlMotor(direction) {
status.innerHTML = `<p>正在${direction === 'forward' ? '前进' : '后退'}...</p>`;
// 记录操作(如果正在录制)
if (isRecording) {
const timestamp = Date.now() - recordingStartTime;
recordedActions.push({ direction, timestamp });
}
// 发送AJAX请求到后端服务器
fetch('/control', {
method: 'POST',
@ -28,6 +41,50 @@ function controlMotor(direction) {
});
}
// 录制按钮点击事件
recordBtn.addEventListener('click', () => {
if (isRecording) {
// 停止录制
isRecording = false;
recordBtn.textContent = '开始录制';
status.innerHTML = `<p>录制完成,共记录 ${recordedActions.length} 个操作</p>`;
} else {
// 开始录制
isRecording = true;
recordedActions = [];
recordingStartTime = Date.now();
recordBtn.textContent = '停止录制';
status.innerHTML = `<p>开始录制...</p>`;
}
});
// 回放按钮点击事件
playbackBtn.addEventListener('click', () => {
if (recordedActions.length === 0) {
status.innerHTML = `<p>没有可回放的操作</p>`;
return;
}
status.innerHTML = `<p>开始回放...</p>`;
// 按时间顺序回放操作
let lastTimestamp = 0;
recordedActions.forEach((action, index) => {
setTimeout(() => {
controlMotor(action.direction);
// 最后一个操作完成后显示回放完成
if (index === recordedActions.length - 1) {
setTimeout(() => {
status.innerHTML = `<p>回放完成</p>`;
}, 2000);
}
}, action.timestamp - lastTimestamp);
lastTimestamp = action.timestamp;
});
});
// 按钮点击事件
forwardBtn.addEventListener('click', () => {
controlMotor('forward');

Binary file not shown.

278
main/camera_test.py Normal file
View File

@ -0,0 +1,278 @@
import cv2
import numpy as np
import time
import signal
import sys
# 信号处理函数用于捕获Ctrl+C
def signal_handler(sig, frame):
print("\n接收到中断信号,程序退出")
sys.exit(0)
# 注册信号处理函数
signal.signal(signal.SIGINT, signal_handler)
print("开始运行摄像头测试程序...")
print(f"OpenCV版本: {cv2.__version__}")
# 尝试打开摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头!")
exit()
print("摄像头打开成功")
# 设置摄像头参数
cap.set(3, 320)
cap.set(4, 240)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
print(f"摄像头分辨率: {cap.get(3)}x{cap.get(4)}")
# 初始化稳定阶段
print("初始化摄像头...")
success_count = 0
for i in range(10):
ret, frame = cap.read()
if not ret:
print(f"{i+1}帧读取失败")
continue
success_count += 1
print(f"{i+1}帧读取成功")
time.sleep(0.1)
print(f"摄像头初始化完成,成功读取{success_count}")
# 读取初始帧
ret, prev_frame = cap.read()
if not ret:
print("无法读取初始帧!")
cap.release()
exit()
print("初始帧读取成功")
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (5,5), 0)
print("初始帧处理完成")
# 记录上一帧目标的位置和面积
prev_cx = None
prev_cy = None
prev_area = None
# 目标跟踪状态
tracking_target = None
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5,5), 0)
# 帧差
diff = cv2.absdiff(prev_gray, gray)
diff = cv2.convertScaleAbs(diff, alpha=2.5)
# 应用形态学操作,去除噪声
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
diff = cv2.morphologyEx(diff, cv2.MORPH_OPEN, kernel)
diff = cv2.morphologyEx(diff, cv2.MORPH_CLOSE, kernel)
_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# ===== 核心:目标检测和跟踪 =====
target = None
max_area = 0
# 找到所有符合条件的轮廓
valid_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 200: # 过滤噪声,增大阈值
continue
if area > 8000: # 过滤大面积假目标(如整个画面),减小阈值
continue
# 计算轮廓的宽高比,过滤不符合垃圾特征的目标
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w) / h if h > 0 else 0
if aspect_ratio > 3 or aspect_ratio < 0.3: # 过滤过于狭长的目标
continue
valid_contours.append((area, cnt, x, y, w, h))
# 如果有有效的轮廓
if valid_contours:
# 如果正在跟踪目标,优先选择与上一目标位置接近的轮廓
if tracking_target is not None and prev_cx is not None and prev_cy is not None:
best_match = None
min_distance = float('inf')
search_radius = 80 # 目标锁定区域半径
for area, cnt, x, y, w, h in valid_contours:
cx = x + w // 2
cy = y + h // 2
# 计算与上一目标的距离
distance = ((cx - prev_cx) ** 2 + (cy - prev_cy) ** 2) ** 0.5
# 只考虑在锁定区域内的目标
if distance > search_radius:
continue
# 如果距离小于阈值,认为是同一个目标
if distance < 50: # 距离阈值
if distance < min_distance:
min_distance = distance
best_match = (area, cnt, x, y, w, h)
# 如果找到匹配的目标
if best_match:
area, cnt, x, y, w, h = best_match
target = cnt
max_area = area
else:
# 如果没有找到匹配的目标,选择最大的轮廓
valid_contours.sort(key=lambda x: x[0], reverse=True)
area, cnt, x, y, w, h = valid_contours[0]
target = cnt
max_area = area
# 开始跟踪新目标
tracking_target = True
else:
# 如果没有正在跟踪的目标,选择最大的轮廓
valid_contours.sort(key=lambda x: x[0], reverse=True)
area, cnt, x, y, w, h = valid_contours[0]
target = cnt
max_area = area
# 开始跟踪新目标
tracking_target = True
# ===== 只处理一个目标 =====
if target is not None:
x, y, w, h = cv2.boundingRect(target)
cx = x + w // 2
cy = y + h // 2
# 目标位置平滑处理
alpha = 0.6
if prev_cx is not None and prev_cy is not None:
cx = int(alpha * prev_cx + (1 - alpha) * cx)
cy = int(alpha * prev_cy + (1 - alpha) * cy)
# 绘制画面中心
center_x = 160 # 画面中心x坐标
center_y = 120 # 画面中心y坐标
cv2.circle(frame, (center_x, center_y), 5, (255, 0, 0), -1)
# 绘制目标矩形和中心
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
print("唯一目标:", cx, cy, "area:", max_area)
# 检测目标是否突然跳变
target_jumped = False
if prev_cx is not None and prev_cy is not None and prev_area is not None:
# 计算位置变化
dx = abs(cx - prev_cx)
dy = abs(cy - prev_cy)
# 计算面积变化比例
area_ratio = max_area / prev_area if prev_area > 0 else 0
# 如果位置变化过大或面积变化过大,认为目标跳变
if dx > 100 or dy > 100 or area_ratio < 0.3 or area_ratio > 3:
target_jumped = True
print("目标突然跳变,可能已离开视野范围")
# 重置跟踪状态
tracking_target = None
# 控制逻辑根据x轴和y轴坐标控制垃圾桶移动
center_x = 160 # 画面中心x坐标
center_y = 120 # 画面中心y坐标
threshold = 20 # 阈值范围
if target_jumped:
# 目标跳变,停止移动
print("目标跳变,停止移动")
# motor.stop()
else:
# 计算目标与中心的偏差
dx = cx - center_x
dy = cy - center_y
# 确定移动方向
if abs(dx) < threshold and abs(dy) < threshold:
# 目标在中心附近,停止移动
print("目标在中心,停止移动")
# motor.stop()
elif abs(dx) < threshold:
# 只在y轴方向有偏差
if dy > threshold:
# 目标在下侧,垃圾桶向前移动
print("目标在下侧,向前移动")
# motor.forward(speed=0.6)
else:
# 目标在上侧,垃圾桶向后移动
print("目标在上侧,向后移动")
# motor.backward(speed=0.6)
elif abs(dy) < threshold:
# 只在x轴方向有偏差
if dx > threshold:
# 目标在右侧,垃圾桶向右移动
print("目标在右侧,向右移动")
# motor.move_right(speed=0.6)
else:
# 目标在左侧,垃圾桶向左移动
print("目标在左侧,向左移动")
# motor.move_left(speed=0.6)
else:
# 在x轴和y轴方向都有偏差使用斜向移动
if dx > threshold and dy > threshold:
# 目标在右下侧,垃圾桶向右前移动
print("目标在右下侧,向右前移动")
# motor.move_right_forward(speed=0.6)
elif dx > threshold and dy < -threshold:
# 目标在右上侧,垃圾桶向右后移动
print("目标在右上侧,向右后移动")
# motor.move_right_backward(speed=0.6)
elif dx < -threshold and dy > threshold:
# 目标在左下侧,垃圾桶向左前移动
print("目标在左下侧,向左前移动")
# motor.move_left_forward(speed=0.6)
else:
# 目标在左上侧,垃圾桶向左后移动
print("目标在左上侧,向左后移动")
# motor.move_left_backward(speed=0.6)
# 更新上一帧的目标信息
prev_cx = cx
prev_cy = cy
prev_area = max_area
else:
# 没有检测到目标,停止移动
print("未检测到目标,停止移动")
# motor.stop()
# 重置上一帧的目标信息
prev_cx = None
prev_cy = None
prev_area = None
# 重置跟踪状态
tracking_target = None
cv2.imshow("tracking", frame)
prev_gray = gray.copy()
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
# motor.stop()

View File

@ -13,31 +13,38 @@
<!-- 手柄风格控制器 -->
<div class="controller">
<!-- 移动控制区域 -->
<div class="movement-controls">
<div class="movement-row">
<button class="btn btn-forward-left" id="forwardLeftBtn"><span>左前</span></button>
<button class="btn btn-forward" id="forwardBtn"><span>前进</span></button>
<button class="btn btn-forward-right" id="forwardRightBtn"><span>右前</span></button>
<div class="controller-layout">
<!-- 左侧:八个方向控制 -->
<div class="dpad">
<div class="dpad-row">
<button class="btn btn-forward-left" id="forwardLeftBtn"><span></span></button>
<button class="btn btn-forward" id="forwardBtn"><span></span></button>
<button class="btn btn-forward-right" id="forwardRightBtn"><span></span></button>
</div>
<div class="dpad-row">
<button class="btn btn-left" id="leftBtn"><span></span></button>
<div class="btn btn-center"></div>
<button class="btn btn-right" id="rightBtn"><span></span></button>
</div>
<div class="dpad-row">
<button class="btn btn-backward-left" id="backwardLeftBtn"><span></span></button>
<button class="btn btn-backward" id="backwardBtn"><span></span></button>
<button class="btn btn-backward-right" id="backwardRightBtn"><span></span></button>
</div>
</div>
<div class="movement-row">
<button class="btn btn-left" id="leftBtn"><span>左移</span></button>
<button class="btn btn-backward" id="backwardBtn"><span>后退</span></button>
<button class="btn btn-right" id="rightBtn"><span>右移</span></button>
<!-- 中间:录制回放控制 -->
<div class="center-controls">
<button class="btn btn-record" id="recordBtn"><span>录制</span></button>
<button class="btn btn-playback" id="playbackBtn"><span>回放</span></button>
</div>
<div class="movement-row">
<button class="btn btn-backward-left" id="backwardLeftBtn"><span>左后</span></button>
<button class="btn btn-rotate-left" id="rotateLeftBtn"><span>左旋转</span></button>
<button class="btn btn-rotate-right" id="rotateRightBtn"><span>右旋转</span></button>
<button class="btn btn-backward-right" id="backwardRightBtn"><span>右后</span></button>
<!-- 右侧:旋转控制 -->
<div class="rotate-controls">
<button class="btn btn-rotate-left" id="rotateLeftBtn"><span></span></button>
<button class="btn btn-rotate-right" id="rotateRightBtn"><span></span></button>
</div>
</div>
<!-- 录制回放控制区域 -->
<div class="record-controls">
<button class="btn btn-record" id="recordBtn"><span>开始录制</span></button>
<button class="btn btn-playback" id="playbackBtn"><span>回放</span></button>
</div>
</div>
<!-- 状态显示区域 -->
@ -48,5 +55,6 @@
<script src="Javascript/control.js"></script>
<script src="Javascript/record.js"></script>
<script src="Javascript/joycon.js"></script>
</body>
</html>

View File

@ -72,25 +72,25 @@ def motor_drive(name, speed):
# ======================
# 基础移动
# ======================
def forward(speed=0.6):
def forward(speed=0.8):
motor_drive("M1", -speed)
motor_drive("M2", -speed)
motor_drive("M3", speed)
motor_drive("M4", speed)
def backward(speed=0.6):
def backward(speed=0.8):
motor_drive("M1", speed)
motor_drive("M2", speed)
motor_drive("M3", -speed)
motor_drive("M4", -speed)
def move_left(speed=0.6):
def move_left(speed=0.8):
motor_drive("M1", -speed)
motor_drive("M2", speed)
motor_drive("M3", speed)
motor_drive("M4", -speed)
def move_right(speed=0.6):
def move_right(speed=0.8):
motor_drive("M1", speed)
motor_drive("M2", -speed)
motor_drive("M3", -speed)
@ -99,13 +99,13 @@ def move_right(speed=0.6):
# ======================
# 旋转
# ======================
def rotate_left(speed=0.6):
def rotate_left(speed=0.8):
motor_drive("M1", -speed)
motor_drive("M2", -speed)
motor_drive("M3", -speed)
motor_drive("M4", -speed)
def rotate_right(speed=0.6):
def rotate_right(speed=0.8):
motor_drive("M1", speed)
motor_drive("M2", speed)
motor_drive("M3", speed)
@ -114,25 +114,25 @@ def rotate_right(speed=0.6):
# ======================
# 斜向移动
# ======================
def move_left_forward(speed=0.6):
def move_left_forward(speed=0.8):
motor_drive("M1", 0)
motor_drive("M2", speed)
motor_drive("M3", 0)
motor_drive("M4", -speed)
def move_right_forward(speed=0.6):
def move_right_forward(speed=0.8):
motor_drive("M1", speed)
motor_drive("M2", 0)
motor_drive("M3", -speed)
motor_drive("M4", 0)
def move_left_backward(speed=0.6):
def move_left_backward(speed=0.8):
motor_drive("M1", -speed)
motor_drive("M2", 0)
motor_drive("M3", speed)
motor_drive("M4", 0)
def move_right_backward(speed=0.6):
def move_right_backward(speed=0.8):
motor_drive("M1", 0)
motor_drive("M2", -speed)
motor_drive("M3", 0)