几年前我在网络冲浪的过程中就发现了个神奇的东西-清醒梦
能够在做梦的过程中意识自己在做梦,并且在意识到后还能控制梦境
这种真实性目前还没有什么虚拟沉浸式设备能够实现
要实现类似的效果,也通常得服用某些物质
虽然我比较感兴趣,不过实际上并未尝试多少次 [总成功次数大约为10次]
主要是影响睡眠,需要在睡着后5-6小时醒来,然后再让自己清醒15-30分钟,最后再执行类似于冥想的方法睡去。
不过也有不影响睡眠的,就是影响清醒的时候了,需要你在清醒的时候时不时的提醒自己验证是否在梦中,以便在做梦的时候发现自己在做梦。
并且我成功入梦后体验不是很好,要不然是四肢动不了,要不然就是睁眼是漆黑一片,梦中开灯也几乎没亮度,或是清晰度很差,很难撑开眼睛。 只有非常少数的梦能够正常踏出卧室门。
今年暑假想着能不能做个能够监测目前是否在做梦并进行提醒的设备
之前就了解过市售的各种清醒梦眼罩或是手表,全都是通过简单的计时,然后到了理论的快速眼动期或是自己设置的时间后就开始发光或震动
![Screenshot_20250913_173821.jpg]()
![Screenshot_20250913_173627.jpg]()
![Screenshot_20250913_173953.jpg]()
(这个是放置在某个敏感部位 到时间震动 试图诱发春梦或清醒梦 看着挺有意思的)
这类设备可能能够增加清醒梦概率的,不过我需要的是真的能监测到梦境并发送信号的
一开始我看我的小米手环就有睡眠监测功能,能够显示出处于深睡,浅睡或是快速眼动期,说不定能简单的通过心率监测判断出睡眠周期
不过后来我看了一个youtube博主,专门测评手表各种监测功能准确性的,实际上手环的睡眠周期监测并不准确
另外我还谷歌搜索了一下,搜出了这个论文
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
![image.png]()
![image.png]()
显示EOG,ECG,EMG 的REM Accuracy (准确率) 都很高
EOG,EMG 的REM Sensitivity (灵敏度) Cohen’s Kappa (一致性 ) 都较高
其中EOG是眼电监测,ECG是心电监测,EMG是监测颏下肌的肌电
肌电监测REM是通过监测颏下肌是否比正常状态的信号还要低,这个可能对监测设备的噪音性能有一定要求,先忽略
心电由于看手表测评精度不高,并且在这个论文内其他参数也比较差,先忽略
脑电必须使用某些厂商的脑电芯片或是套件,淘宝常见的就是金牛座的脑电波模块,据介绍会输出原始脑波,然后会提供放松度和专注度数值。 不过我有点不相信这个的准确性,并且看这电路板不像是好调试的。
眼电就简单许多了,只需要监测眼球转动,噪音性能要求应该会比上面所说的肌电脑电小许多。
![tb_image_share_1757937170247.jpg.png]()
![Screenshot_20250915_195320.jpg]()
单纯搜索眼电设备
![image.png]()
![image.png]()
![image.png]()
95db的总增益 5.6万倍的放大
滤波范围 0.02Hz-30Hz
mv单位
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
![image.png]()
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
搜索 眼电图 清醒梦 搜出了一篇9年前的个人网站上的文章,通过监测眼电来实现清醒梦在9年前就有人尝试过了
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
![image.png]()
![image.png]()
![image.png]()
![image.png]()
![image.png]()
约60db总增益 900倍
滤波范围1Hz-17Hz
既然已经有了先例并且效果看着很不错,那就直接copy过来。
肌电监测设备:
把原电路的集成arduino删掉,然后输入改成使用耳机接口连接导联线,另外AD620 RG电阻修改为680,实际为67db总增益 2200倍放大。
导联线红色为耳机接口的地,接额头。其余两个为耳机接口的左声道右声道,接左右眼的外眦
![tb_image_share_1757763347048.jpg.png]()
LMV358是双路运放,可以一路用于放大,一路做电压跟随器
不过原电路图是用的两个双路的LMV358,每个分别做运放和电压跟随器,另外一路单纯悬空,那我也就这样
![image.png]()
肌肉电信号放大:
1.通过导联线将信号输入至AD620仪表放大器 放大73.6倍 :
G = 49.4 kΩ/R_G + 1
REF参考电压接入2.5V的虚拟地
2.AD620放大后通过RC高通滤波器 截止频率设置为1hz
fc = 1 / (2πRC)
3.滤波后通过LMV358运放 放大30倍
R4/R3
4.最后通过两个RC低通滤波器 设置截止频率为17HZ,输出最终信号至arduino ADC。
虚拟地:
LMV358做电压跟随器 设置虚拟地为+2.5V,作为肌电信号的中点
PCB部分:
走线弯弯绕绕的也没有铺铜
只是大概确保输入和输出没有上下重叠或平行
![image.png]()
设备实物:
arduino开发板是国产的,ad620[25元]和lmv358[0.4元]是淘宝优信买的原装
供电用的是两节18650锂电池,不要用任何源头是220v的电源供电,除了噪音还有安全性。
全部用的杜邦线连接,不是好的连接方式,可能随便碰一碰就会有断触或波动。
![IMG_20250916_120250.jpg]()
![IMG_20250916_131545.jpg]()
[由于睡眠中有翻身之类的操作,有时候会扯到导联线,这个排针就是牵扯导致的弯曲]
![IMG_20250915_213800.jpg]()
![IMG_20250915_213718.jpg]()
心电电极片
如果导联线的纽扣扣上去了,就不能拔出来,要不然可能会导致接触不良
一开始我还以为是元件问题,我去重新焊接了一下RG电阻,重新插上就没用了,我还在想这电子元件这么脆弱吗,然后下单新的AD620
后面我用力按着头上的电极片,波形又正常了。。。
原来是我焊接的时候为了方便把扣上电极的导联线拔出来了
arduino程序部分:
// AI大师写的
#include <EEPROM.h>
#include <SoftwareSerial.h>
SoftwareSerial BT(10, 11);
const int EEPROM_START_ADDRESS = 0;
const int MAX_RECORDS = 1000;
const int MAX_RECORD_SIZE = sizeof(unsigned long) + 1;
int nextRecordAddress = EEPROM_START_ADDRESS;
const byte RECORD_TERMINATOR = 0xFF;
const int SAMPLING_PERIOD = 20;
const float VOLTAGE_THRESHOLD = 2.7;
const int TRIGGER_COUNT_THRESHOLD = 6;
const unsigned long TIME_WINDOW = 80000;
const int CYCLE_COUNT = 16;
unsigned long triggerTimes[TRIGGER_COUNT_THRESHOLD];
int triggerCount = 0;
bool pin2Triggered = false;
enum TriggerState {
IDLE,
PWM_ACTIVE,
LED_FLASHING,
COOLDOWN
};
TriggerState state = IDLE;
unsigned long stateStartTime = 0;
unsigned long lastActionTime = 0;
int cycleStep = 0;
int flashCount = 0;
void setup() {
Serial.begin(9600);
Serial.println("bluetooth is ready!");
BT.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
for (int i = 0; i < TRIGGER_COUNT_THRESHOLD; i++) {
triggerTimes[i] = 0;
}
EEPROM.get(EEPROM_START_ADDRESS + MAX_RECORDS * MAX_RECORD_SIZE, nextRecordAddress);
if (nextRecordAddress < EEPROM_START_ADDRESS || nextRecordAddress >= EEPROM_START_ADDRESS + MAX_RECORDS * MAX_RECORD_SIZE) {
nextRecordAddress = EEPROM_START_ADDRESS;
}
}
void loop() {
unsigned long now = millis();
// ===== Voltage sampling and trigger detection =====
static unsigned long lastSampleTime = 0;
static unsigned long lastDebounceTime = 0;
if (state == COOLDOWN) {
// During cooldown, only check for exiting cooldown
if (now - stateStartTime >= 2UL * 60UL * 1000UL) { // 2 minutes
pin2Triggered = false;
state = IDLE;
triggerCount = 0;
for (int i = 0; i < TRIGGER_COUNT_THRESHOLD; i++) {
triggerTimes[i] = 0;
}
}
int sensorValue = analogRead(A0);
float voltage = sensorValue * (5.0 / 1023.0);
Serial.println(voltage);
BT.println(voltage);
return; // Skip all other processing during cooldown
}
int sensorValue = analogRead(A0);
float voltage = sensorValue * (5.0 / 1023.0);
Serial.println(voltage);
BT.println(voltage);
if (now - lastSampleTime >= SAMPLING_PERIOD) {
lastSampleTime = now;
bool isTriggered = voltage > VOLTAGE_THRESHOLD;
if (isTriggered && !pin2Triggered) {
if (now - lastDebounceTime < 600) return;
lastDebounceTime = now;
BT.println("move");
digitalWrite(LED_BUILTIN, HIGH);
for (int i = 0; i < TRIGGER_COUNT_THRESHOLD - 1; i++) {
triggerTimes[i] = triggerTimes[i + 1];
}
triggerTimes[TRIGGER_COUNT_THRESHOLD - 1] = now;
triggerCount = min(triggerCount + 1, TRIGGER_COUNT_THRESHOLD);
if (triggerCount >= TRIGGER_COUNT_THRESHOLD) {
unsigned long earliest = triggerTimes[0];
unsigned long latest = triggerTimes[0];
for (int i = 1; i < TRIGGER_COUNT_THRESHOLD; i++) {
if (triggerTimes[i] < earliest) earliest = triggerTimes[i];
if (triggerTimes[i] > latest) latest = triggerTimes[i];
}
if (latest - earliest <= TIME_WINDOW) {
BT.println("dream");
if (nextRecordAddress + MAX_RECORD_SIZE <= EEPROM_START_ADDRESS + MAX_RECORDS * MAX_RECORD_SIZE) {
unsigned long recordTime = now / 1000;
EEPROM.put(nextRecordAddress, recordTime);
EEPROM.write(nextRecordAddress + sizeof(unsigned long), RECORD_TERMINATOR);
EEPROM.put(EEPROM_START_ADDRESS + MAX_RECORDS * MAX_RECORD_SIZE, nextRecordAddress += MAX_RECORD_SIZE);
Serial.println(recordTime);
} else {
nextRecordAddress = EEPROM_START_ADDRESS;
}
triggerCount = 0;
for (int i = 0; i < TRIGGER_COUNT_THRESHOLD; i++) triggerTimes[i] = 0;
pin2Triggered = true;
state = PWM_ACTIVE;
stateStartTime = now;
cycleStep = 0;
analogWrite(3, 0);
}
}
}
else {
digitalWrite(LED_BUILTIN, LOW);
}
}
// ===== State machine for PWM and LED flashing =====
switch (state) {
case PWM_ACTIVE:
if (now - stateStartTime >= 2000UL * cycleStep) {
if (cycleStep < CYCLE_COUNT * 2) {
if (cycleStep % 2 == 0) {
analogWrite(3, 30);
digitalWrite(4, HIGH);
} else {
analogWrite(3, 0);
digitalWrite(4, LOW);
}
cycleStep++;
} else {
analogWrite(3, 0);
digitalWrite(4, LOW);
state = LED_FLASHING;
stateStartTime = now;
flashCount = 0;
lastActionTime = now;
}
}
break;
case LED_FLASHING:
if (now - lastActionTime >= (flashCount % 2 == 0 ? 2000 : 1000)) {
digitalWrite(4, flashCount % 2 == 0 ? HIGH : LOW);
flashCount++;
lastActionTime = now;
}
if (flashCount >= 2) {
digitalWrite(4, LOW);
state = COOLDOWN;
stateStartTime = now;
}
break;
case COOLDOWN:
// Handled at the start of loop
break;
case IDLE:
default:
break;
}
}
一开始是单纯的daly,触发了2.7v就记录移动然后delay一会,如果触发了超过几次就触发for循环,用delay延迟,开灯关灯开蜂鸣器关蜂鸣器
后面需要记录波形,就不能用delay了,要不然会阻塞
达到一定电压就记录移动一次,记录后延迟一段时间确保单次上升波形不重复触发
眼动的波形不一定像理想的在一定时间内"上升 到顶 下降 过中点 继续下降到底点 回中"
目前就是单纯的达到电压就触发,可以加个与前一次数据差值太大就忽略掉
80秒内记录6次就判断为在做梦
设计有两个周期,第一个周期蜂鸣器 led都触发,第二个周期单纯触发led
为了不那么容易唤醒又能有一定的提醒效果,不过实际上用不上
另外我的笔记本漏电严重 ,用手摸没事,但是如果用arduino连接笔记本usb插入和拔出的瞬间,就会遭受电击,电击瞬间眼睛的画面会发生变化,我记得那个时候眼前会出现闪光。
解决办法就是拔掉笔记本充电器,或是进行接地
我把在同一个插线排的台式机接地了[一端鳄鱼夹接显卡的支撑挡板,一端接窗户],效果也很不错
之后还买了个蓝牙模块[HC05],一开始买错了,买成BLE蓝牙了。。。
虽然模块标注RX,TX只支持3.3v的信号输入输出,不过实际上输入5V 输出3.3V也没问题,
并且蓝牙的RX,TX是不能接到arduino的RX,TX接口上,怎么试蓝牙都没反应
我看许多教程都是这么接的,并且我用示波器测试通过USB串口发送消息,RX是有对应的方波的,也是相同的波特率
[那个arduino的RX,TX引脚和USB的是同一路]
这问题折腾我半天
最后我看有的是用SoftwareSerial库,使用别的引脚作为串口,我照着写就可以了。。。
肌电校准方法:
两手握着一个校准纸,两手臂伸直, 盯着中点一会后迅速将眼神转向左或右'触发点',然后返回'中点'
检查是否触发眼球运动检测 ,如果没触发调低阈值,
校准完毕后可再将眼神转向左或右'不触发点',检查是否未触发。
目前我这个灵敏度大概是触发点离中点8cm
然后就是爆照
![IMG_20250912_001957.jpg]()
video_20250912_002221.mp4 点击下载
lv_0_20250914212020.mp4 点击下载
使用的串口调试软件是Serial Studio Pro,这个软件显示的波形是经过类似平滑处理过的,并不是实际波形
往哪边转就产生对应的正向或反向电压
触发效果:
video_20250912_005242.mp4 点击下载
市售的一般都是做成眼罩,然后led直接贴着眼睛
不过我测试贴着和远距离的感受都差不多,都是眼前一片亮光,只是功率需要大些
蓝牙接收程序:
# AI大师写的
import serial
import datetime
import time
import threading
from playsound import playsound
def connect_serial(com_port, baudrate):
"""尝试连接串口"""
try:
ser = serial.Serial(
port=com_port,
baudrate=baudrate,
timeout=0.1 # 减小超时时间,提高响应性
)
print(f"Connected to {com_port} at {baudrate} baud")
return ser
except Exception as e:
print(f"Failed to connect to {com_port}: {str(e)}")
return None
def play_music(music_file):
"""在独立线程中播放音乐"""
try:
print(f"Playing music: {music_file}")
playsound(music_file)
print("Music playback finished")
except Exception as e:
print(f"Error playing music: {str(e)}")
def main():
# 用户输入COM端口
com_port = input("Please enter COM port (e.g., COM3): ").strip()
baudrate = 9600
data_timeout = 15 # println数据可能有间隔,适当延长超时时间
music_file = "path/to/your/music.mp3" # 音乐文件路径
# 生成带时间戳的输出文件名
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"serial_data_{timestamp}.txt"
ser = None
reconnect_delay = 5 # 重连延迟
last_data_time = time.time()
music_thread = None
# 数据缓冲区,用于累积不完整的数据
data_buffer = ""
try:
with open(output_file, 'a', encoding='utf-8') as f:
while True:
# 确保串口连接有效
if not ser or not ser.is_open:
ser = connect_serial(com_port, baudrate)
if ser:
print(f"Saving data to {output_file}")
last_data_time = time.time()
data_buffer = "" # 重连时清空缓冲区
else:
print(f"Retrying connection in {reconnect_delay} seconds...")
time.sleep(reconnect_delay)
continue
try:
# 检查是否有数据可读
if ser.in_waiting > 0:
# 读取所有可用数据
raw_data = ser.read(ser.in_waiting)
last_data_time = time.time()
try:
# 解码数据并添加到缓冲区
new_text = raw_data.decode('utf-8', errors='replace')
data_buffer += new_text
# 处理println发送的完整行(以\n或\r\n结尾)
while '\n' in data_buffer:
line_end = data_buffer.find('\n')
complete_line = data_buffer[:line_end]
data_buffer = data_buffer[line_end + 1:] # 保留剩余数据
# 清理println数据(去除\r和多余空白字符)
clean_line = complete_line.rstrip('\r\n\t ')
# 处理非空的println输出
if clean_line: # println可能输出空行,这里过滤掉
timestamp = datetime.datetime.now().strftime("%m-%d %H:%M:%S.%f")[:-3]
output = f"{timestamp}\t{clean_line}"
print(output)
f.write(output + '\n')
f.flush()
# 检查是否收到"dream"指令
if clean_line.lower() == "dream":
if music_thread is None or not music_thread.is_alive():
# music_thread = threading.Thread(target=play_music, args=(music_file,))
# music_thread.start()
pass
# 如果缓冲区中的数据太长且没有换行符(异常情况)
# 这可能表示println数据被严重分割或存在传输问题
if len(data_buffer) > 500: # println通常不会太长
print(f"Warning: Unusually long line detected ({len(data_buffer)} chars)")
if data_buffer.strip():
timestamp = datetime.datetime.now().strftime("%m-%d %H:%M:%S.%f")[:-3]
output = f"{timestamp}\t[INCOMPLETE] {data_buffer.strip()}"
print(output)
f.write(output + '\n')
f.flush()
data_buffer = ""
except UnicodeDecodeError:
# 处理二进制数据
timestamp = datetime.datetime.now().strftime("%m-%d %H:%M:%S.%f")[:-3]
output = f"{timestamp}\t[HEX] {raw_data.hex()}"
print(output)
f.write(output + '\n')
f.flush()
data_buffer = "" # 清空缓冲区
# 检查数据接收超时
if time.time() - last_data_time > data_timeout:
print(f"No data received for {data_timeout} seconds. Attempting to reconnect...")
if ser and ser.is_open:
ser.close()
ser = None
data_buffer = "" # 超时时清空缓冲区
time.sleep(reconnect_delay)
continue
time.sleep(0.005) # 短暂休眠,println数据需要更快响应
except (serial.SerialException, OSError) as e:
print(f"Serial error: {str(e)}. Attempting to reconnect...")
if ser and ser.is_open:
ser.close()
ser = None
data_buffer = "" # 错误时清空缓冲区
time.sleep(reconnect_delay)
except KeyboardInterrupt:
print("\nStopping data collection")
break
except Exception as e:
print(f"Unexpected error: {str(e)}")
break
except Exception as e:
print(f"Failed to initialize: {str(e)}")
finally:
if ser and ser.is_open:
ser.close()
print("Serial port closed")
# 等待音乐线程结束
if music_thread and music_thread.is_alive():
music_thread.join()
if __name__ == "__main__":
main()
使用的serial库,一开始我想找专门操作蓝牙的库,找到的都太老了不支持高版本的python
后面我知道这个windows蓝牙是怎么用的了
连接这个COM6串口相当于执行"连接已配对过的HC-05的蓝牙"。
有触发dream电脑同步播放自定义音频的功能不过从未实际使用过
![image.png]()
![image.png]()
数据可视化程序:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.dates as mdates
import tkinter as tk
from datetime import datetime
import pandas as pd
import os
# 设置matplotlib中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
def load_data_pandas(file_path):
"""使用pandas快速加载数据"""
try:
df = pd.read_csv(file_path, sep='\t', header=None, names=['timestamp', 'value'])
df['datetime'] = pd.to_datetime(df['timestamp'], format='%m-%d %H:%M:%S.%f', errors='coerce')
event_mask = df['value'].isin(['move', 'dream'])
events_df = df[event_mask].copy()
numeric_df = df[~event_mask].copy()
numeric_df['value'] = pd.to_numeric(numeric_df['value'], errors='coerce')
numeric_df = numeric_df.dropna()
mask = (numeric_df['value'] > 1) & (numeric_df['value'] < 5) & (numeric_df['value'] != 2.00)
float_mask = numeric_df['timestamp'].str.contains(r'\.', na=False)
numeric_df = numeric_df[mask & float_mask]
return (numeric_df['datetime'].values,
numeric_df['value'].values,
events_df['datetime'].values,
events_df['value'].values)
except Exception as e:
print(f"Error loading file: {e}")
return None, None, None, None
def smart_time_format(ax):
"""智能设置时间轴格式,根据当前显示范围自动调整"""
try:
# 获取当前x轴显示范围
xlim = ax.get_xlim()
start_date = mdates.num2date(xlim[0])
end_date = mdates.num2date(xlim[1])
# 计算时间跨度(秒)
time_span = (end_date - start_date).total_seconds()
# 根据时间跨度智能选择刻度间隔和格式
if time_span <= 30: # 30秒内
ax.xaxis.set_major_locator(mdates.SecondLocator(interval=5))
ax.xaxis.set_minor_locator(mdates.SecondLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
elif time_span <= 120: # 2分钟内
ax.xaxis.set_major_locator(mdates.SecondLocator(interval=10))
ax.xaxis.set_minor_locator(mdates.SecondLocator(interval=5))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
elif time_span <= 600: # 10分钟内
ax.xaxis.set_major_locator(mdates.SecondLocator(interval=30))
ax.xaxis.set_minor_locator(mdates.SecondLocator(interval=10))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
elif time_span <= 1800: # 30分钟内
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=2))
ax.xaxis.set_minor_locator(mdates.MinuteLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
elif time_span <= 3600: # 1小时内
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=5))
ax.xaxis.set_minor_locator(mdates.MinuteLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
elif time_span <= 10800: # 3小时内
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=15))
ax.xaxis.set_minor_locator(mdates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
elif time_span <= 86400: # 1天内 - 使用10分钟刻度
ax.xaxis.set_major_locator(mdates.MinuteLocator(interval=10))
ax.xaxis.set_minor_locator(mdates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
elif time_span <= 259200: # 3天内
ax.xaxis.set_major_locator(mdates.HourLocator(interval=2))
ax.xaxis.set_minor_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))
elif time_span <= 604800: # 1周内
ax.xaxis.set_major_locator(mdates.HourLocator(interval=6))
ax.xaxis.set_minor_locator(mdates.HourLocator(interval=2))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))
elif time_span <= 2592000: # 1个月内
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_minor_locator(mdates.HourLocator(interval=12))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
else: # 更长时间
# 根据时间跨度动态调整天数间隔
days = int(time_span / 86400)
if days <= 90: # 3个月内
interval = max(1, days // 30)
elif days <= 365: # 1年内
interval = max(7, days // 30)
else: # 超过1年
interval = max(30, days // 12)
ax.xaxis.set_major_locator(mdates.DayLocator(interval=interval))
ax.xaxis.set_minor_locator(mdates.DayLocator(interval=max(1, interval//3)))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
# 设置刻度标签旋转角度
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
except Exception as e:
print(f"时间轴格式化出错: {e}")
# 如果出错,使用默认格式
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
def create_plot(timestamps, values, event_timestamps, events):
"""创建绘图界面"""
root = tk.Tk()
root.title("数据可视化工具")
# 设置tkinter中文字体
try:
import tkinter.font as tkFont
default_font = tkFont.nametofont("TkDefaultFont")
default_font.configure(family="Microsoft YaHei")
root.option_add("*Font", default_font)
except:
pass
# 创建控制面板
control_frame = tk.Frame(root)
control_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
# 创建matplotlib图形
fig, ax = plt.subplots(figsize=(15, 8))
def on_xlims_change(ax):
"""x轴范围改变时的回调函数"""
smart_time_format(ax)
fig.canvas.draw_idle()
def update_plot():
"""更新图形"""
ax.clear()
print(f"\n显示数据点数: {len(timestamps):,}")
# 绘制波形
ax.plot(timestamps, values, '-', linewidth=0.5, alpha=0.8,
label='数据波形', color='blue')
# 绘制事件标记
if event_timestamps is not None and len(event_timestamps) > 0:
move_plotted = False
dream_plotted = False
for event_time, event_type in zip(event_timestamps, events):
if hasattr(event_time, 'to_pydatetime'):
event_time = event_time.to_pydatetime()
# 在显示数据中找最近点
if len(timestamps) > 0:
if hasattr(timestamps[0], 'to_pydatetime'):
time_diffs = np.abs([(t.to_pydatetime() - event_time).total_seconds()
for t in timestamps])
elif isinstance(timestamps[0], np.datetime64):
event_time_np = np.datetime64(event_time)
time_diffs = np.abs((timestamps - event_time_np) / np.timedelta64(1, 's'))
else:
time_diffs = np.abs([(t - event_time).total_seconds() for t in timestamps])
closest_idx = np.argmin(time_diffs)
y_value = values[closest_idx]
if event_type == 'move':
label = '移动' if not move_plotted else ""
ax.plot(event_time, y_value, 'bo', markersize=5, alpha=0.8, label=label)
move_plotted = True
elif event_type == 'dream':
label = '梦境' if not dream_plotted else ""
ax.plot(event_time, y_value, 'ro', markersize=5, alpha=0.8, label=label)
dream_plotted = True
ax.set_xlabel('时间')
ax.set_ylabel('数值')
title = f'数据可视化\n'
title += f'数据点: {len(timestamps):,}点 | 事件: {len(events) if events is not None else 0}'
ax.set_title(title)
ax.grid(True, alpha=0.3)
# 设置初始时间轴格式
smart_time_format(ax)
# 注册x轴范围变化的回调函数
ax.callbacks.connect('xlim_changed', on_xlims_change)
ax.legend()
plt.tight_layout()
canvas.draw()
# 更新按钮
update_btn = tk.Button(control_frame, text="更新图形", command=update_plot,
bg='lightgreen', font=('Arial', 10, 'bold'))
update_btn.pack(side=tk.RIGHT, padx=10)
# 嵌入matplotlib到Tkinter
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# 添加导航工具栏
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
# 状态信息
status_frame = tk.Frame(root)
status_frame.pack(side=tk.BOTTOM, fill=tk.X)
event_count = len(events) if events is not None and len(events) > 0 else 0
status_text = f"数据: {len(timestamps):,} 点 | 事件: {event_count} | 支持智能时间轴缩放"
status_label = tk.Label(status_frame, text=status_text)
status_label.pack()
# 初始绘图
print("开始初始化图形...")
update_plot()
root.mainloop()
def get_file_path():
"""获取文件路径,支持拖拽输入"""
print("=" * 60)
print("数据可视化工具 - 文件路径输入")
print("=" * 60)
print()
print("请将文件拖拽到此窗口,然后按回车:")
print("(支持直接拖拽,会自动处理路径中的引号和空格)")
file_path = input().strip()
# 处理拖拽文件路径中的引号
if file_path.startswith('"') and file_path.endswith('"'):
file_path = file_path[1:-1]
elif file_path.startswith("'") and file_path.endswith("'"):
file_path = file_path[1:-1]
# 验证文件是否存在
if not os.path.exists(file_path):
print(f"\n
错误: 文件不存在 - {file_path}")
print("请检查文件路径是否正确")
return None
# 显示文件信息
file_size = os.path.getsize(file_path) / (1024 * 1024) # MB
print(f"\n
文件验证成功:")
print(f" 路径: {file_path}")
print(f" 大小: {file_size:.2f} MB")
print()
return file_path
def main():
print("启动数据可视化工具...")
# 获取文件路径
file_path = get_file_path()
if file_path is None:
input("按任意键退出...")
return
print(f"\n开始加载数据: {os.path.basename(file_path)}")
print("=" * 50)
start_time = datetime.now()
print("使用 Pandas 快速加载...")
timestamps, values, event_timestamps, events = load_data_pandas(file_path)
load_time = datetime.now() - start_time
print("=" * 50)
print(f"
数据加载完成,耗时: {load_time.total_seconds():.2f}秒")
if timestamps is not None and len(timestamps) > 0:
event_count = len(events) if events is not None and len(events) > 0 else 0
print(f"
成功加载:")
print(f" 数据点: {len(timestamps):,} 个")
print(f" 事件数: {event_count} 个")
print(f" 时间范围: {timestamps[0]} 到 {timestamps[-1]}")
print()
print("
启动图形界面...")
print()
create_plot(timestamps, values, event_timestamps, events)
else:
print("
未能加载数据,可能的原因:")
print(" 1. 文件格式不正确")
print(" 2. 文件内容为空")
print(" 3. 数据格式不符合要求")
print()
print("要求的数据格式:")
print(" 时间戳\\t数值")
print(" 例: 01-01 12:30:45.123\\t2.56")
input("\n按任意键退出...")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n程序被用户中断")
except Exception as e:
print(f"\n
程序出现错误: {e}")
input("按任意键退出...")
去除了所有等于2.00v的数据,如果不去除的话就会这样,我怀疑是arduino adc的问题,差不多每几分钟就有一次,什么时候可以用示波器监测一下看看是哪个的问题
![image.png]()
确认与眼球运动有关的波形参考:
![image.png]()
![image.png]()
![image.png]()
![image.png]()
9月15日 包括身体运动或是眼球转动的
![image.png]()
快速眼动期眼球实际运动参考视频:
REM sleep - Rapid Eye Movement Sleep - REMS.mp4 点击下载
小姑娘的快速眼动睡眠_p1.mp4 点击下载
如果使用红外摄像头近距离拍摄,使用某些现成的能够识别出眼部区域的模型,将眼部区域单独截取出来。然后自己标注训练一个能够识别闭眼眼球运动的模型,这样是不是就可以做出一个无接触式快速眼动期识别设备
。。。。。搜了一下 已经有了
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
每晚的眼电图:
目前没一个成功,中途闪光+蜂鸣器也是照样睡着,没有被唤醒也不记得梦里有什么闪光或蜂鸣器的声音
我有尝试蜂鸣器声音调的很高,为了唤醒以确认刚才是不是在做梦,不过那个时候我醒来立马就又睡着了,不太记得了,最近可以再重新尝试下
我现在可能还缺个监控摄像头,看看触发是否是因为我在那个时间段运动导致的,并且看看设备在那个时间到底有没有成功执行
暑假[7/10]到现在中途有一个清醒梦不过与这个设备无关
等什么时候成功了再整理下这个眼电图,现在就随便放点
触发红色dream标记后有冷却期 冷却期内波形正常记录但是不会触发move蓝色标记
前4.5小时不开启蜂鸣器和头顶灯光,为了保证前4.5个小时的重要睡眠,并且通常生动及印象深刻的梦都出现在后半段
![image.png]()
黄色为小米手环显示清醒时段
绿色为根据眼电图手动标注的快速眼动期时段
蓝色为小米手环显示快速眼动期时段
9月15号 5:10 开启了蜂鸣器 以及头顶灯光
![Screenshot_20250915_135417.jpg]()
手环显示的第二次清醒的时候我把肌电拔了,然后又迷迷糊糊的睡着了
起床后想起做了一个比较长的梦 不记得梦里有眼前闪光或是蜂鸣器的声音
大概是与我开三轮车去摩托车驾校 然后找不到摩托车驾校 回家 翻叠在一起的纸质资料 看见xxx然后xxxx
具体些包括 真实的三轮车结构 挂挡 拧油门 声音 差点出车祸 手机 导航 小卖部 人 xxxxx
这个梦可能与蜂鸣器有些关联,因为有点类似车辆喇叭声 但是主要应该是因为我现实在考三轮车驾照
我就不标记非常零星的红色了
通常一晚的快速眼动期会越来越长
![image.png]()
5:10闹钟时间 起来喝水然后上了个厕所
拔导联线接头前
![image.png]()
总体
![image.png]()
躺回去后
![image.png]()
![image.png]()
6:40闹钟时间
![image.png]()
第一个标注疑似快速眼动期的部分截图
![image.png]()
![image.png]()
![image.png]()
第二个标注疑似快速眼动期的部分截图
![image.png]()
![image.png]()
![image.png]()
第三个标注疑似rem的部分截图
![image.png]()
![image.png]()
超过4v的信号 针对这种过于笔直,与上次电压差值非常大的可以进行过滤
![image.png]()
![image.png]()
![image.png]()
![image.png]()
这个波形正常一些没有这么笔直
8月3日
![Screenshot_20250914_202706.jpg]()
![image.png]()
2:33 可能是噪音干扰
![image.png]()
5:40那段时间的 根据波形来看 确实是眼睛移动了
![image.png]()
7:41 一样
![image.png]()
8月12日
![Screenshot_20250914_181551.jpg]()
![image.png]()
9月13日用错误的9月11日的小米手环标注 碰巧入睡时间差17分钟 清醒时间差1分钟 [中间睡觉的时候拉扯到眼电设备了,电源断掉了]
![Screenshot_20250916_142148.jpg]()
睡觉的时候拉扯到设备,设备电源线被扯掉了
![image.png]()
9月13日用9月13日的小米手环标注
![Screenshot_20250914_194528.jpg]()
![image.png]()
9月14日
![image.png]()
截取9月14日 3:39左右的噪音强度 2.55v中点 最高2.63v波动 大概3%的波动
![image.png]()
截取9月14日 9:49左右的噪音强度 2.55v中点 最高2.58v波动 大概1.1%的波动
![image.png]()
之前的截图
坐在凳子上
![微信图片_20250917122241.png]()
躺在床上
![微信图片_20250917122248.png]()
![image.png]()
眼电记录数据.7z31.61MB7Z0次下载
1.09Gb压成31Mb了 不愧是一大堆重复内容的文本
应该选择怎么样的外部刺激才容易进入到梦境:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
![image.png]()
![image.png]()
![image.png]()
![image.png]()
另外分享一个我知道的一个研究清醒梦的网站
很多有关清醒梦的新闻或是论文都是来源于这些人
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
![image.png]()
![image.png]()
![image.png]()
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Transferring of music from lucid dreams into reality.mp4 点击下载
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
原理大概就是规定 左手握拳等于这个声音 右手握拳等于那个声音
在梦中握拳也能在现实中检测到肌肉电信号的变化
关于清醒梦的危害:
有可能影响睡眠,可能发生于某些熟练者身上,正常睡眠过程中容易不可控的意识到是在做梦,从而导致一晚的睡眠质量都不好
还有可能不可控的遭受比较真实的恐怖体验或各种不适的体验,相当于做噩梦
另外还有些报告者没有主观上造假的,但是可能并不代表对应部位有真实的问题或损伤:
醒来心脏不舒服
梦中受伤疼痛的部位醒来也有疼痛感
最后虽然没有成功将梦转为清醒梦
但是可能可以用于进入清醒梦后让现实捕捉到你在做清醒梦并且执行你预先设定的一些东西
比如播放一些环境音,看看在清醒梦中对梦境环境的构建有没有什么影响
还有一些睡觉不会完全闭眼的人,我在想如果在做梦的时候,在他眼前合适的距离播放视频,他的梦境会不会受视频影响