Browse Source

fix final

finalv2
Backpack 2 months ago
parent
commit
45ab3114e3
16 changed files with 88 additions and 222 deletions
  1. +2
    -2
      package.json
  2. BIN
      src-py/__pycache__/VoiceController.cpython-38.pyc
  3. BIN
      src-py/router/__pycache__/__init__.cpython-38.pyc
  4. BIN
      src-py/router/__pycache__/ws.cpython-38.pyc
  5. +3
    -43
      src-py/router/ws.py
  6. +2
    -2
      src-tauri/Cargo.toml
  7. +1
    -1
      src-tauri/src/lib.rs
  8. +4
    -4
      src-tauri/tauri.conf.json
  9. +1
    -1
      src-tauri/tauri.macos.conf.json
  10. +2
    -2
      src/hand_landmark/VideoDetector.vue
  11. +10
    -7
      src/hand_landmark/detector.ts
  12. +57
    -145
      src/hand_landmark/gesture_handler.ts
  13. +1
    -1
      src/locales/en.ts
  14. +1
    -1
      src/locales/zh.ts
  15. +1
    -1
      src/store/app.ts
  16. +3
    -12
      src/view/mainWindow/Update.vue

+ 2
- 2
package.json View File

@ -8,8 +8,8 @@
"dev": "vite",
"build:py": "pyinstaller --noconfirm --distpath src-tauri/bin/ main_win.spec",
"build:py-mac": "pyinstaller --noconfirm --distpath src-tauri/bin/ main_mac.spec",
"build:icons": "npx tauri icon public/lazyeat.png",
"build:icons-mac": "npx tauri icon public/lazyeat.png --output src-tauri/icons/mac",
"build:icons": "npx tauri icon public/WaveControl.png",
"build:icons-mac": "npx tauri icon public/WaveControl.png --output src-tauri/icons/mac",
"build": "vite build",
"preview": "vite preview",
"tauri": "npx tauri",

BIN
src-py/__pycache__/VoiceController.cpython-38.pyc View File


BIN
src-py/router/__pycache__/__init__.cpython-38.pyc View File


BIN
src-py/router/__pycache__/ws.cpython-38.pyc View File


+ 3
- 43
src-py/router/ws.py View File

@ -50,15 +50,13 @@ class WebSocketMessageType:
# 鼠标操作类型
MOUSE_MOVE = "mouse_move"
MOUSE_CLICK = "mouse_click"
MOUSE_SCROLL_UP = "mouse_scroll_up"
MOUSE_SCROLL_DOWN = "mouse_scroll_down"
# 键盘操作类型
SEND_KEYS = "send_keys"
# 语音操作类型
VOICE_RECORD = "voice_record"
VOICE_STOP = "voice_stop"
class MessageSender:
@ -170,36 +168,6 @@ class GestureHandler:
print(f"执行按键失败: {e}")
class VoiceHandler:
"""语音处理控制器"""
def __init__(self):
from VoiceController import VoiceController
self.controller: Optional[VoiceController] = None
try:
self.controller = VoiceController()
except Exception as e:
print(f"语音控制器初始化失败: {e}")
async def start_recording(self, websocket: WebSocket) -> None:
"""开始录音"""
if self.controller and not self.controller.is_recording:
self.controller.start_record_thread()
async def stop_recording(
self, websocket: WebSocket, gesture_handler: GestureHandler
) -> None:
"""停止录音并处理结果"""
if self.controller and self.controller.is_recording:
self.controller.stop_record()
# 获取识别结果并输入
text = self.controller.transcribe_audio()
if text:
gesture_handler.keyboard.type(text)
gesture_handler.keyboard.tap(Key.enter)
@router.websocket("/ws_wavecontrol")
async def websocket_endpoint(websocket: WebSocket):
@ -210,12 +178,10 @@ async def websocket_endpoint(websocket: WebSocket):
active_connection = websocket
gesture_handler = GestureHandler()
voice_handler = VoiceHandler()
try:
while True:
data_str = await websocket.receive_text()
await _handle_message(data_str, websocket, gesture_handler, voice_handler)
await _handle_message(data_str, websocket, gesture_handler)
except WebSocketDisconnect:
active_connection = None
except Exception as e:
@ -227,7 +193,6 @@ async def _handle_message(
data_str: str,
websocket: WebSocket,
gesture_handler: GestureHandler,
voice_handler: VoiceHandler,
) -> None:
"""处理WebSocket消息"""
try:
@ -248,13 +213,8 @@ async def _handle_message(
elif message.type == WebSocketMessageType.SEND_KEYS:
gesture_handler.send_keys(data["key_str"])
# 处理语音操作
elif message.type == WebSocketMessageType.VOICE_RECORD:
await voice_handler.start_recording(websocket)
elif message.type == WebSocketMessageType.VOICE_STOP:
await voice_handler.stop_recording(websocket, gesture_handler)
except json.JSONDecodeError:
print("无效的JSON数据")
except Exception as e:
print(f"处理消息失败: {e}")

+ 2
- 2
src-tauri/Cargo.toml View File

@ -1,7 +1,7 @@
[package]
name = "Lazyeat"
name = "WaveControl"
version = "0.3.11"
description = "Lazyeat 手势识别"
description = "WaveControl 手势识别"
authors = ["https://github.com/maplelost"]
edition = "2021"

+ 1
- 1
src-tauri/src/lib.rs View File

@ -8,7 +8,7 @@ fn greet(name: &str) -> String {
async fn start_sidecar(app: tauri::AppHandle) -> Result<String, String> {
let sidecar = app
.shell()
.sidecar("Lazyeat Backend")
.sidecar("WaveControl Backend")
.map_err(|e| format!("无法找到sidecar: {}", e))?;
let (_rx, _child) = sidecar

+ 4
- 4
src-tauri/tauri.conf.json View File

@ -1,8 +1,8 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lazyeat",
"productName": "WaveControl",
"version": "0.3.11",
"identifier": "com.Lazyeat.maplelost",
"identifier": "com.WaveControl.maplelost",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "Lazyeat",
"title": "WaveControl",
"width": 800,
"height": 600,
"devtools": true
@ -24,7 +24,7 @@
},
"bundle": {
"active": true,
"externalBin": ["bin/backend-py/Lazyeat Backend"],
"externalBin": ["bin/backend-py/WaveControl Backend"],
"resources": {
"bin/backend-py/_internal": "_internal/",
"../model": "model/",

+ 1
- 1
src-tauri/tauri.macos.conf.json View File

@ -1,6 +1,6 @@
{
"$schema": "https://schema.tauri.app/config/2",
"identifier": "com.Lazyeat.maplelost",
"identifier": "com.WaveControl.maplelost",
"bundle": {
"resources": [],
"macOS": {

+ 2
- 2
src/hand_landmark/VideoDetector.vue View File

@ -6,11 +6,11 @@
<!-- <li>
删除文件夹
<n-tag size="small">
%LOCALAPPDATA%\com.Lazyeat.maplelost\EBWebView
%LOCALAPPDATA%\com.WaveControl.maplelost\EBWebView
</n-tag>
</li> -->
<li>
进入<n-tag size="small">%LOCALAPPDATA%\com.Lazyeat.maplelost</n-tag>
进入<n-tag size="small">%LOCALAPPDATA%\com.WaveControl.maplelost</n-tag>
</li>
<li>删除<n-tag size="small">EBWebView</n-tag></li>
<li>重新启动程序</li>

+ 10
- 7
src/hand_landmark/detector.ts View File

@ -13,16 +13,20 @@ export enum HandGesture {
// 食指和拇指同时竖起 - 鼠标左键点击
INDEX_AND_THUMB_UP = "index_and_thumb_up",
// 三根手指同时竖起 - 滚动屏幕
THREE_FINGERS_UP = "three_fingers_up",
// ok手势 - 滚动屏幕
SCROLL_GESTURE_2 = "scroll_gesture_2",
// 四根手指同时竖起
// 四根手指同时竖起 - enter
FOUR_FINGERS_UP = "four_fingers_up",
// 五根手指同时竖起 - 暂停/开始 识别
STOP_GESTURE = "stop_gesture",
// 6手势 - 语音识别
VOICE_GESTURE_START = "voice_gesture_start",
VOICE_GESTURE_STOP = "voice_gesture_stop",
// 其他手势
DELETE_GESTURE = "delete_gesture",
@ -207,10 +211,9 @@ export class Detector {
// 鼠标左键点击手势
["1,1,0,0,0", HandGesture.INDEX_AND_THUMB_UP],
// 滚动屏幕手势
["0,1,1,1,0", HandGesture.THREE_FINGERS_UP],
["1,0,1,1,1", HandGesture.SCROLL_GESTURE_2],
// 滚动屏幕手势 ok
["0,0,1,1,1", HandGesture.SCROLL_GESTURE_2],
["1,0,1,1,1", HandGesture.SCROLL_GESTURE_2],
// 四根手指同时竖起
["0,1,1,1,1", HandGesture.FOUR_FINGERS_UP],
@ -218,7 +221,7 @@ export class Detector {
// 五根手指同时竖起 - 暂停/开始 识别
["1,1,1,1,1", HandGesture.STOP_GESTURE],
// 拇指和食指同时竖起 - 语音识别
// 6手势- 语音识别
["1,0,0,0,1", HandGesture.VOICE_GESTURE_START],
// 其他手势

+ 57
- 145
src/hand_landmark/gesture_handler.ts View File

@ -13,15 +13,14 @@ enum WsDataType {
// 鼠标操作类型
MOUSE_MOVE = "mouse_move",
MOUSE_CLICK = "mouse_click",
MOUSE_SCROLL_UP = "mouse_scroll_up",
MOUSE_SCROLL_DOWN = "mouse_scroll_down",
// 键盘操作类型
SEND_KEYS = "send_keys",
// 语音操作类型
VOICE_RECORD = "voice_record",
VOICE_STOP = "voice_stop",
// 窗口操作类型
MOUSE_SCROLL_UP = "mouse_scroll_up",
MOUSE_SCROLL_DOWN = "mouse_scroll_down",
}
interface WsData {
@ -81,7 +80,7 @@ export class TriggerAction {
const message: WsData = {
type: data.type,
msg: data.msg || "",
title: data.title || "Lazyeat",
title: data.title || "WaveControl",
duration: data.duration || 1,
data: data.data || {},
};
@ -101,17 +100,6 @@ export class TriggerAction {
});
}
scrollUp() {
this.send({
type: WsDataType.MOUSE_SCROLL_UP,
});
}
scrollDown() {
this.send({
type: WsDataType.MOUSE_SCROLL_DOWN,
});
}
sendKeys(key_str: string) {
this.send({
@ -120,15 +108,16 @@ export class TriggerAction {
});
}
voiceRecord() {
scrollUp() {
this.send({
type: WsDataType.VOICE_RECORD,
type: WsDataType.MOUSE_SCROLL_UP,
});
}
voiceStop() {
scrollDown() {
this.send({
type: WsDataType.VOICE_STOP,
type: WsDataType.MOUSE_SCROLL_DOWN,
});
}
}
@ -146,30 +135,28 @@ export class GestureHandler {
private previousGestureCount: number = 0;
private minGestureCount: number = 5;
// 鼠标移动参数
// 屏幕参数,用于将摄像头图像坐标映射到实际屏幕坐标
private screen_width: number = window.screen.width;
private screen_height: number = window.screen.height;
private smoothening = 7; // 平滑系数
private prev_loc_x: number = 0;
private prev_loc_y: number = 0;
private prev_three_fingers_y: number = 0; // 添加三根手指上一次的 Y 坐标
private prev_scroll2_y: number = 0;
// 时间间隔参数
private lastClickTime: number = 0;
private lastScrollTime: number = 0;
private lastFullScreenTime: number = 0;
private lastDeleteTime: number = 0;
// 时间间隔常量(毫秒)
private readonly CLICK_INTERVAL = 500; // 点击间隔
private smoothening = 7; // 平滑系数,用于减少抖动
private prev_loc_x: number = 0; // 上一帧鼠标X坐标
private prev_loc_y: number = 0; // 上一帧鼠标Y坐标
private lastClickTime: number = 0; // 上次点击时间戳(用于点击节流)
private readonly CLICK_INTERVAL = 500; // 点击最小间隔(ms)
private lastkeyTime: number = 0;
private readonly sendkeyINTERVAL = 1500; // 全屏切换间隔
private app_store: any; // 存储应用状态,比如视频宽高、边界设置等
private prev_scroll_y: number = 0;
private lastScrollTime: number = 0;
private readonly SCROLL_INTERVAL = 100; // 滚动间隔
private readonly FULL_SCREEN_INTERVAL = 1500; // 全屏切换间隔
// 语音识别参数
private voice_recording: boolean = false;
private lastDeleteTime: number = 0;
private app_store: any;
constructor() {
this.triggerAction = new TriggerAction();
this.app_store = use_app_store();
@ -223,16 +210,17 @@ export class GestureHandler {
this.screen_height
);
// 应用平滑处理
// 加入平滑处理,缓解抖动
screenX =
this.prev_loc_x + (screenX - this.prev_loc_x) / this.smoothening;
screenY =
this.prev_loc_y + (screenY - this.prev_loc_y) / this.smoothening; // 消除抖动
// 更新上一帧坐标
this.prev_loc_x = screenX;
this.prev_loc_y = screenY;
// 移动鼠标
// 更新状态,触发鼠标移动指令
this.app_store.sub_windows.x = screenX + 10;
this.app_store.sub_windows.y = screenY;
this.triggerAction.moveMouse(screenX, screenY);
@ -242,68 +230,44 @@ export class GestureHandler {
}
/**
* -
* +
*/
private handleMouseClick() {
const now = Date.now();
if (now - this.lastClickTime < this.CLICK_INTERVAL) {
return;
return; // 距离上次点击时间太短,忽略(节流)
}
this.lastClickTime = now;
// 发送鼠标点击指令
this.triggerAction.clickMouse();
}
/**
* -
* -
*/
private handleScroll(hand: HandInfo) {
const indexTip = this.getFingerTip(hand, 1);
const middleTip = this.getFingerTip(hand, 2);
const ringTip = this.getFingerTip(hand, 3);
if (!indexTip || !middleTip || !ringTip) {
this.prev_three_fingers_y = 0;
return;
}
const now = Date.now();
if (now - this.lastScrollTime < this.SCROLL_INTERVAL) {
return;
}
this.lastScrollTime = now;
// 计算三根手指的平均 Y 坐标
const currentY = (indexTip.y + middleTip.y + ringTip.y) / 3;
// 如果是第一次检测到手势,记录当前 Y 坐标
if (this.prev_three_fingers_y === 0) {
this.prev_three_fingers_y = currentY;
return;
}
// 计算 Y 坐标的变化
const deltaY = currentY - this.prev_three_fingers_y;
// 如果变化超过阈值,则触发滚动
if (Math.abs(deltaY) > 0.008) {
if (deltaY < 0) {
// 手指向上移动,向上滚动
this.triggerAction.scrollUp();
} else {
// 手指向下移动,向下滚动
this.triggerAction.scrollDown();
private handleFourFingers() {
try {
const key_str = this.app_store.config.four_fingers_up_send || "f";
const now = Date.now();
if (now - this.lastkeyTime < this.sendkeyINTERVAL) {
return;
}
// 更新上一次的 Y 坐标
this.prev_three_fingers_y = currentY;
this.lastkeyTime = now;
this.triggerAction.sendKeys(key_str);
} catch (error) {
console.error("处理四指手势失败:", error);
}
}
// 拇指和食指捏合,滚动屏幕
private handleScroll2(hand: HandInfo) {
private handlescroll(hand: HandInfo) {
const indexTip = this.getFingerTip(hand, 1);
const thumbTip = this.getFingerTip(hand, 0);
if (!indexTip || !thumbTip) {
this.prev_scroll2_y = 0;
this.prev_scroll_y = 0;
return;
}
@ -325,18 +289,18 @@ export class GestureHandler {
distance >
this.app_store.config.scroll_gesture_2_thumb_and_index_threshold
) {
this.prev_scroll2_y = 0;
this.prev_scroll_y = 0;
return;
}
// 如果是第一次检测到捏合,记录当前 Y 坐标
if (this.prev_scroll2_y === 0) {
this.prev_scroll2_y = indexTip.y;
if (this.prev_scroll_y === 0) {
this.prev_scroll_y = indexTip.y;
return;
}
// 计算 Y
const deltaY = indexTip.y - this.prev_scroll2_y;
const deltaY = indexTip.y - this.prev_scroll_y;
// 如果变化超过阈值,则触发滚动
if (Math.abs(deltaY) > 0.008) {
@ -348,52 +312,11 @@ export class GestureHandler {
this.triggerAction.scrollDown();
}
// 更新上一次的 Y 坐标
this.prev_scroll2_y = indexTip.y;
}
}
/**
* -
*/
private handleFourFingers() {
try {
const key_str = this.app_store.config.four_fingers_up_send || "f";
const now = Date.now();
if (now - this.lastFullScreenTime < this.FULL_SCREEN_INTERVAL) {
return;
}
this.lastFullScreenTime = now;
this.triggerAction.sendKeys(key_str);
} catch (error) {
console.error("处理四指手势失败:", error);
}
}
/**
* -
*/
async handleVoiceStart() {
if (this.voice_recording) {
return;
}
await this.app_store.sub_window_info("开始语音识别");
this.voice_recording = true;
this.triggerAction.voiceRecord();
}
/**
* -
*/
async handleVoiceStop() {
if (!this.voice_recording) {
return;
this.prev_scroll_y = indexTip.y;
}
await this.app_store.sub_window_success("停止语音识别");
this.voice_recording = false;
this.triggerAction.voiceStop();
}
/**
*
*/
@ -433,12 +356,13 @@ export class GestureHandler {
}
/**
*
* landmark
* fingerIndex: 0=, 1=, 2=, ...
*/
private getFingerTip(hand: HandInfo, fingerIndex: number) {
if (!hand) return null;
const tipIndices = [4, 8, 12, 16, 20];
const tipIndices = [4, 8, 12, 16, 20]; // 手指指尖 landmark 的索引
return hand.landmarks[tipIndices[fingerIndex]];
}
@ -467,12 +391,6 @@ export class GestureHandler {
return;
}
// 只要切换手势就停止语音识别
if (gesture !== HandGesture.VOICE_GESTURE_START && this.voice_recording) {
this.handleVoiceStop();
return;
}
// 鼠标移动手势直接执行,不需要连续确认
if (gesture === HandGesture.ONLY_INDEX_UP) {
this.handleIndexFingerUp(hand);
@ -486,17 +404,11 @@ export class GestureHandler {
this.handleMouseClick();
break;
case HandGesture.SCROLL_GESTURE_2:
this.handleScroll2(hand);
this.handlescroll(hand);
break;
// case HandGesture.THREE_FINGERS_UP:
// this.handleScroll(hand);
// break;
case HandGesture.FOUR_FINGERS_UP:
this.handleFourFingers();
break;
case HandGesture.VOICE_GESTURE_START:
this.handleVoiceStart();
break;
case HandGesture.DELETE_GESTURE:
this.handleDelete();
break;

+ 1
- 1
src/locales/en.ts View File

@ -40,7 +40,7 @@ export default {
"识别框高": "Recognition box height",
// 通知
"Lazyeat": "Lazyeat",
"WaveControl": "WaveControl",
"提示": "Tip",
"停止语音识别": "Stop Voice Recognition",
"手势识别": "Gesture Recognition",

+ 1
- 1
src/locales/zh.ts View File

@ -38,7 +38,7 @@ export default {
"可以通过右键->检查->控制台->捏合手势->查看当前距离": "可以通过右键->检查->控制台->捏合手势->查看当前距离",
// 通知
"Lazyeat": "Lazyeat",
"WaveControl": "WaveControl",
"提示": "提示",
"停止语音识别": "停止语音识别",
"手势识别": "手势识别",

+ 1
- 1
src/store/app.ts View File

@ -18,7 +18,7 @@ export const use_app_store = defineStore("app-store", {
config: {
auto_start: false,
show_window: false,
four_fingers_up_send: "f",
four_fingers_up_send: "enter",
selected_camera_id: "",
// 识别框

+ 3
- 12
src/view/mainWindow/Update.vue View File

@ -1,21 +1,12 @@
<template>
<div class="update-container">
<n-card title="开发" hoverable>
<n-card title="1" hoverable>
<n-space vertical size="large">
<div class="dev-section">
<n-alert type="info" title="开发者招募">
<n-alert type="info" title="1">
<template #icon>
<n-icon><code /></n-icon>
</template>
<n-space vertical>
<span>有安卓开发的大佬吗可以联系我</span>
<span>欢迎加入QQ群
<n-button text type="primary" tag="a" href="https://jq.qq.com/?_wv=1027&k=452246065" target="_blank">
452246065
</n-button>
讨论
</span>
</n-space>
</n-alert>
</div>
@ -25,7 +16,7 @@
<n-space vertical>
<div class="version-header">
<n-tag type="success" size="small">v0.3.9</n-tag>
<n-button text type="primary" tag="a" href="https://github.com/maplelost/lazyeat/releases/tag/v0.3.9" target="_blank">
<n-button text type="primary" tag="a" href="https://github.com/maplelost/WaveControl/releases/tag/v0.3.9" target="_blank">
下载地址
</n-button>
</div>

Loading…
Cancel
Save