diff --git a/launcher2.py b/launcher2.py index f449e0f..e5b6975 100644 --- a/launcher2.py +++ b/launcher2.py @@ -51,7 +51,9 @@ default_arg = { 'preset': 'Low', 'mouse_audio_input': False, 'audio_sensitivity': '0.02', - 'audio_threshold': '10.0' + 'audio_threshold': '10.0', + 'blink_interval': '5.0', + 'breath_cycle': 'inf' } try: @@ -308,11 +310,18 @@ class LauncherPanel(wx.Panel): addOption('mouse_audio_input', title='Audio Input', desc='启用WASAPI音频输入控制嘴部动作', type=1) addOption('audio_sensitivity', title='Audio Sensitivity', desc='音频灵敏度,控制音频对嘴部动作的影响程度', type=2) addOption('audio_threshold', title='Audio Threshold', desc='音频阈值,低于此值的音频将被忽略', type=2) + addOption('blink_interval', title='Blink Interval', desc='设置眨眼间隔时间', + choices=['No Blink', '3 seconds', '5 seconds', '7 seconds'], + mapping=['inf', '3.0', '5.0', '7.0']) addOption('min_cutoff', title='Min CutOff', desc='输入滤波频率截断,\n越小越平滑,越大静止时越灵敏', type=3, mapper=min_cutoff_mapper) addOption('beta', title='Beta', desc='输入滤波速度补偿,\n越小越平滑,越大运动时越灵敏', type=3, mapper=beta_mapper) + addOption('breath_cycle', title='Breath Cycle', desc='设置呼吸循环时间', + choices=['No Breath', '3 seconds', '5 seconds', '7 seconds'], + mapping=['inf', '3.0', '5.0', '7.0']) + addOption('output', title='Output', desc='选择输出目标', choices=['Spout2', 'OBS VirtualCam', 'Debug Output'], mapping=[0, 1, 2]) @@ -390,8 +399,10 @@ class LauncherPanel(wx.Panel): self.optionSizer.Hide(self.optionDict['mouse_audio_input']) self.optionSizer.Hide(self.optionDict['audio_sensitivity']) self.optionSizer.Hide(self.optionDict['audio_threshold']) + self.optionSizer.Hide(self.optionDict['blink_interval']) else: self.optionSizer.Show(self.optionDict['mouse_audio_input']) + self.optionSizer.Show(self.optionDict['blink_interval']) # Update audio fields based on checkbox state audioInputChoice() @@ -533,11 +544,20 @@ class LauncherPanel(wx.Panel): if args['audio_threshold']: run_args.append('--audio_threshold') run_args.append(str(args['audio_threshold'])) + # Add blink interval for mouse input + if args['blink_interval']: + run_args.append('--blink_interval') + run_args.append(str(args['blink_interval'])) elif args['input'] == 4: if len(args['osf']): run_args.append('--osf_input') run_args.append(args['osf']) + # Add breath cycle option + if args['breath_cycle']: + run_args.append('--breath_cycle') + run_args.append(str(args['breath_cycle'])) + if args['output'] == 0: run_args.append('--output_spout2') elif args['output'] == 1: diff --git a/src/args.py b/src/args.py index d7ad390..6800e06 100644 --- a/src/args.py +++ b/src/args.py @@ -24,6 +24,8 @@ parser.add_argument('--osf_input', type=str) parser.add_argument('--mouse_audio_input', action='store_true') parser.add_argument('--audio_sensitivity', type=float, default=0.02) parser.add_argument('--audio_threshold', type=float, default=10.0) +parser.add_argument('--blink_interval', type=float, default=5.0) +parser.add_argument('--breath_cycle', type=float, default=float('inf')) parser.add_argument('--output_virtual_cam', action='store_true') parser.add_argument('--output_spout2', action='store_true') diff --git a/src/face_mesh_client.py b/src/face_mesh_client.py index 68a09d8..a5352f3 100644 --- a/src/face_mesh_client.py +++ b/src/face_mesh_client.py @@ -67,8 +67,8 @@ class FaceMeshClientProcess(Process): mouth_eye_vector = [0.0] * 27 pose_vector = [0.0] * 6 - mouth_eye_vector[2] = min(eye_l_h_temp, eye_r_h_temp) - mouth_eye_vector[3] = min(eye_l_h_temp, eye_r_h_temp) + mouth_eye_vector[2] = max(eye_l_h_temp, eye_r_h_temp) + mouth_eye_vector[3] = max(eye_l_h_temp, eye_r_h_temp) mouth_eye_vector[14] = mouth_ratio * 2.0 diff --git a/src/mouse_client.py b/src/mouse_client.py index 8a0ad75..62c92f8 100644 --- a/src/mouse_client.py +++ b/src/mouse_client.py @@ -79,6 +79,14 @@ class MouseClientProcess(Process): } last_time : float = time.perf_counter() interval : float = 1.0 / 60 # 60 FPS + + # 眨眼相关参数 + blink_interval = args.blink_interval # 眨眼间隔时间(秒) + blink_duration = 0.25 # 眨眼持续时间(秒) + last_blink_time = time.perf_counter() + + # 呼吸循环参数 + breath_start_time = time.perf_counter() print("Mouse Input Running at 60 FPS") position_vector = np.array([0, 0, 0, 1], dtype=np.float32) @@ -89,11 +97,36 @@ class MouseClientProcess(Process): head_eye_reduce = 0.6 head_slowness = 0.2 + # 计算眨眼效果 + current_time = time.perf_counter() + time_since_last_blink = current_time - last_blink_time + + # 每隔 blink_interval 秒眨一次眼 + if time_since_last_blink >= blink_interval: + last_blink_time = current_time + time_since_last_blink = 0 + + # 计算眨眼动画(使用尖锐的 sin 函数) + if time_since_last_blink < blink_duration: + # 在眨眼持续时间内,使用 sin 函数从 0 升到接近 1 再返回 0 + # 使用 sin^2 使曲线更尖锐 + blink_progress = time_since_last_blink / blink_duration * np.pi + blink_value = np.sin(blink_progress) ** 2 + eye_close = blink_value * 1.0 # 升到1,返回到 0 + else: + eye_close = 0.0 + + # 计算呼吸效果(使用 sin 函数,在 breath_cycle 时间内从 0 到 1 再到 0) + breath_elapsed = (current_time - breath_start_time) % args.breath_cycle + # 使用 sin 函数,让值在一个周期内从 0 -> 1 -> 0 + # sin 在 0 到 π 之间从 0 到 1 到 0 + breath_value = np.sin(breath_elapsed / args.breath_cycle * np.pi) + # 获取音频音量并映射到 mouth_ratio current_mouth_ratio = audio_filter(self.audio_volume.value, time.perf_counter()) if self.audio_running else 0 mouse_data = { - 'eye_l_h_temp': 0, - 'eye_r_h_temp': 0, + 'eye_l_h_temp': eye_close, + 'eye_r_h_temp': eye_close, 'mouth_ratio': current_mouth_ratio, 'eye_y_ratio': np.interp(pos[1], [posLimit[1], posLimit[3]], [1, -1]) * eye_limit[1], 'eye_x_ratio': np.interp(pos[0], [posLimit[0], posLimit[2]], [1, -1]) * eye_limit[0], @@ -136,6 +169,7 @@ class MouseClientProcess(Process): pose_vector[2] = z_angle pose_vector[3] = pose_vector[1] pose_vector[4] = pose_vector[2] + pose_vector[5] = breath_value model_input_arr = eyebrow_vector model_input_arr.extend(mouth_eye_vector)