欢迎光临
我们一直在努力

AI 驱动的家庭场景识别:自动切换影音、休息与工作模式界面

AI 驱动的家庭场景识别:自动切换影音、休息与工作模式界面

一、引子:模式切换不应该是一个按钮

在智能家居产品评审会上,产品经理指着设计稿说:"这里加一个模式切换按钮,用户点击后弹出四个选项:影音模式、阅读模式、休息模式、工作模式。"

技术上行得通,交互上是个灾难。模式切换按钮是场景自动化的反面——它把"根据上下文自动判断应该进入什么模式"的智能需求,退化成了"请你手动告诉我你想干什么"的传统交互。

真正的场景自动化应该是无感的。用户走进客厅、打开投影仪、拉上窗帘——这三个动作的组合已经告诉系统"我要看电影了"。系统的任务不是弹出确认框问"是否开启影音模式?",而是静默执行:灯光调暗到 10%、氛围灯设为暖色调、音响切到 HDMI ARC、空调设为静音模式。只在首次执行时给出一个轻微的通知:"已自动切换到影音模式,点击可调整"。

场景识别的技术本质是一个多传感器融合的意图分类问题。输入是设备状态变化、时间、位置、人体感应等多维信号,输出是一个场景标签和置信度。难点不在分类算法本身,而在分类的时机——既不能反应太慢(用户都开始看电影了灯光还没暗),也不能反应太快(用户只是路过客厅拿个东西就被判定为"要休息")。

二、底层机制:多信号融合的场景分类器

场景识别需要一组弱信号的组合推理,单个信号都不可靠,但多个信号的交叉验证可以大幅提升置信度。

关键设计决策在于置信度分级执行策略。不是所有场景识别结果都应该自动执行。将置信度分为三档:

  • 高置信度(> 0.85):投影仪开机 + 窗帘关闭 + 时间是晚上 + 人在客厅持续 10 秒以上 → 自动切换影音模式,通知:"已切换至影音模式"
  • 中置信度(0.6 – 0.85):窗帘关闭 + 时间晚上 + 但投影仪未开 → 建议切换:"检测到你可能想看视频,需要切换到影音模式吗?"
  • 低置信度(< 0.6):仅窗帘关闭 → 忽略,等待更多信号

分类器的核心输入信号权重如下:

  • 设备主动操作(用户点了什么):权重 0.4
  • 人体传感器数据:权重 0.25
  • 时间上下文:权重 0.2
  • 历史行为模式:权重 0.15

三、生产级代码:场景识别引擎

/**
* 家庭场景识别引擎
*
* 基于多传感器信号融合的意图分类器。
* 输入:原始传感器事件流
* 输出:场景标签 + 置信度 + 执行策略
*/

// 场景标签
type SceneLabel =
| 'movie_time' // 影音模式
| 'reading' // 阅读模式
| 'resting' // 休息模式
| 'working' // 工作模式
| 'dining' // 用餐模式
| 'leaving' // 离家模式
| 'sleeping' // 睡眠模式
| 'unknown'; // 无法识别

// 传感器事件
interface SensorEvent {
type: 'device_state' | 'presence' | 'time' | 'audio';
deviceId?: string;
property?: string;
value: number | boolean | string;
timestamp: number;
}

// 场景识别结果
interface SceneRecognition {
label: SceneLabel;
confidence: number; // 0-1
strategy: 'auto' | 'suggest' | 'ignore';
reasoning: string[]; // 可解释的推理步骤
suggestedActions: SceneAction[];
}

// 场景动作
interface SceneAction {
deviceId: string;
action: string;
params: Record<string, any>;
priority: number;
}

/**
* 场景识别器
*
* 使用加权规则引擎而非深度学习模型,确保:
* 1. 推理过程可解释(调试时能看懂为什么触发)
* 2. 冷启动友好(不需要训练数据)
* 3. 延迟极低(< 50ms,适合实时场景)
*/
class SceneRecognizer {
// 滑动窗口:保留最近 30 秒的传感器事件
private eventWindow: SensorEvent[] = [];
private readonly WINDOW_MS = 30000;

// 场景识别规则表
// 每条规则定义了一个场景的信号组合和权重
private readonly rules: SceneRule[] = [
{
label: 'movie_time',
signals: [
{ type: 'device_state', value: ['projector_power_on', 'tv_power_on'], weight: 0.3 },
{ type: 'device_state', value: ['curtain_closed'], weight: 0.2 },
{ type: 'time', value: ['evening', 'night'], weight: 0.15 },
{ type: 'presence', value: ['person_in_living_room'], weight: 0.2 },
{ type: 'audio', value: ['movie_sound'], weight: 0.15 },
],
},
{
label: 'sleeping',
signals: [
{ type: 'time', value: ['late_night'], weight: 0.3 },
{ type: 'presence', value: ['person_in_bedroom'], weight: 0.25 },
{ type: 'device_state', value: ['all_lights_off'], weight: 0.25 },
{ type: 'device_state', value: ['bedroom_curtain_closed'], weight: 0.2 },
],
},
{
label: 'leaving',
signals: [
{ type: 'device_state', value: ['lock_locked'], weight: 0.3 },
{ type: 'presence', value: ['no_one_home'], weight: 0.4 },
{ type: 'time', value: ['morning'], weight: 0.15 },
{ type: 'device_state', value: ['all_lights_off'], weight: 0.15 },
],
},
{
label: 'reading',
signals: [
{ type: 'device_state', value: ['reading_lamp_on'], weight: 0.4 },
{ type: 'presence', value: ['person_stationary'], weight: 0.2 },
{ type: 'time', value: ['evening'], weight: 0.2 },
{ type: 'audio', value: ['silence'], weight: 0.2 },
],
},
];

// 历史场景记录(用于模式学习)
private sceneHistory: { label: SceneLabel; timestamp: number }[] = [];

/**
* 接收传感器事件并触发场景识别
*
* 使用去抖动策略:事件发生后等待 2 秒,
* 收集窗口内的所有相关事件再统一分析
*/
private debounceTimer: number | null = null;

onSensorEvent(event: SensorEvent): void {
this.eventWindow.push(event);

// 清理过期事件
const cutoff = Date.now() – this.WINDOW_MS;
this.eventWindow = this.eventWindow.filter(
(e) => e.timestamp > cutoff
);

// 去抖:2 秒内连续事件只触发一次识别
if (this.debounceTimer) clearTimeout(this.debounceTimer);
this.debounceTimer = window.setTimeout(() => {
this.recognize();
}, 2000);
}

/**
* 执行场景识别
*/
recognize(): SceneRecognition {
let bestLabel: SceneLabel = 'unknown';
let bestConfidence = 0;
let bestReasoning: string[] = [];
let bestActions: SceneAction[] = [];

// 遍历所有场景规则
for (const rule of this.rules) {
let confidence = 0;
const reasoning: string[] = [];
const matchedSignals: string[] = [];

// 检查每条信号
for (const signal of rule.signals) {
const isMatched = this.matchSignal(
signal,
this.eventWindow
);

if (isMatched) {
confidence += signal.weight;
matchedSignals.push(this.describeSignal(signal));
} else {
reasoning.push(`未检测到: ${this.describeSignal(signal)}`);
}
}

// 应用历史模式加成
const historyBoost = this.getHistoryBoost(rule.label);
confidence += historyBoost;

reasoning.unshift(
`场景「${rule.label}」匹配度: ${(confidence * 100).toFixed(0)}%`,
`匹配信号: ${matchedSignals.join(', ') || '无'}`
);

if (confidence > bestConfidence) {
bestConfidence = confidence;
bestLabel = rule.label;
bestReasoning = reasoning;
bestActions = this.getActionsForScene(rule.label);
}
}

// 置信度封顶在 1.0
bestConfidence = Math.min(bestConfidence, 1.0);

return {
label: bestLabel,
confidence: bestConfidence,
strategy: this.determineStrategy(bestConfidence, bestLabel),
reasoning: bestReasoning,
suggestedActions: bestActions,
};
}

/**
* 信号匹配检查
*
* 在事件窗口中查找匹配的传感器事件
*/
private matchSignal(
signal: SceneSignal,
events: SensorEvent[]
): boolean {
const matchingEvents = events.filter((e) => {
if (e.type !== signal.type) return false;

// 值匹配(支持数组多值匹配)
if (Array.isArray(signal.value)) {
return signal.value.some((v) => this.valueMatches(e.value, v));
}
return this.valueMatches(e.value, signal.value);
});

return matchingEvents.length > 0;
}

/**
* 值匹配逻辑
*/
private valueMatches(
actual: number | boolean | string,
expected: number | boolean | string
): boolean {
// 字符串模糊匹配
if (typeof actual === 'string' && typeof expected === 'string') {
return actual.toLowerCase().includes(expected.toLowerCase());
}
return actual === expected;
}

/**
* 信号描述(用于可解释性)
*/
private describeSignal(signal: SceneSignal): string {
const descriptions: Record<string, string> = {
projector_power_on: '投影仪已开机',
curtain_closed: '窗帘已关闭',
all_lights_off: '全部灯光已关闭',
lock_locked: '门锁已上锁',
no_one_home: '家中无人',
person_in_living_room: '客厅有人',
person_in_bedroom: '卧室有人',
person_stationary: '人处于静止状态',
evening: '当前时段:傍晚',
night: '当前时段:夜间',
late_night: '当前时段:深夜',
morning: '当前时段:早晨',
silence: '环境安静',
reading_lamp_on: '阅读灯已开启',
};
return descriptions[String(signal.value)] || String(signal.value);
}

/**
* 历史模式加成
*
* 最近 7 天同一时段该场景出现频次越高,加成越大
*/
private getHistoryBoost(label: SceneLabel): number {
const now = new Date();
const currentHour = now.getHours();
const dayOfWeek = now.getDay();

// 查找最近 7 天同一时段的历史
const recentMatches = this.sceneHistory.filter((h) => {
const hDate = new Date(h.timestamp);
const hourDiff = Math.abs(hDate.getHours() – currentHour);
return h.label === label && hourDiff <= 1;
});

// 7 天中出现了 4 天以上,加成 0.1
return recentMatches.length >= 4 ? 0.1 : 0;
}

/**
* 确定执行策略
*/
private determineStrategy(
confidence: number,
label: SceneLabel
): 'auto' | 'suggest' | 'ignore' {
if (confidence < 0.55) return 'ignore';

// 离家模式即使中等置信度也自动执行(安全优先)
if (label === 'leaving' && confidence > 0.7) return 'auto';

// 睡眠模式需要高置信度
if (label === 'sleeping' && confidence > 0.85) return 'auto';

// 通用策略
if (confidence > 0.85) return 'auto';
if (confidence > 0.55) return 'suggest';
return 'ignore';
}

/**
* 获取场景对应的操作序列
*/
private getActionsForScene(label: SceneLabel): SceneAction[] {
const actionMap: Record<string, SceneAction[]> = {
movie_time: [
{ deviceId: 'living-light-main', action: 'setBrightness', params: { value: 5 }, priority: 1 },
{ deviceId: 'living-light-ambient', action: 'setColorTemp', params: { value: 3200 }, priority: 2 },
{ deviceId: 'curtain-living', action: 'setPosition', params: { value: 0 }, priority: 1 },
{ deviceId: 'speaker-main', action: 'setInput', params: { value: 'hdmi_arc' }, priority: 3 },
{ deviceId: 'ac-living', action: 'setMode', params: { value: 'quiet' }, priority: 4 },
],
sleeping: [
{ deviceId: 'all-lights', action: 'setPower', params: { value: false }, priority: 1 },
{ deviceId: 'curtain-bedroom', action: 'setPosition', params: { value: 0 }, priority: 1 },
{ deviceId: 'thermostat', action: 'setTemperature', params: { value: 26 }, priority: 2 },
],
leaving: [
{ deviceId: 'all-lights', action: 'setPower', params: { value: false }, priority: 1 },
{ deviceId: 'all-curtains', action: 'setPosition', params: { value: 0 }, priority: 1 },
{ deviceId: 'ac-all', action: 'setPower', params: { value: false }, priority: 2 },
{ deviceId: 'lock-front', action: 'lock', params: {}, priority: 0 },
],
};

return actionMap[label] || [];
}

/**
* 记录场景切换(用于历史学习)
*/
recordScene(label: SceneLabel): void {
this.sceneHistory.push({ label, timestamp: Date.now() });
// 保留最近 30 天数据
const cutoff = Date.now() – 30 * 24 * 3600 * 1000;
this.sceneHistory = this.sceneHistory.filter(
(h) => h.timestamp > cutoff
);
}
}

// 类型补充
interface SceneSignal {
type: 'device_state' | 'presence' | 'time' | 'audio';
value: string | number | boolean | (string | number | boolean)[];
weight: number;
}

interface SceneRule {
label: SceneLabel;
signals: SceneSignal[];
}

四、边界分析

误触发是最大的信任杀手:场景识别的错误分两类——误报(false positive)和漏报(false negative)。误报的代价远大于漏报。系统在用户没有看电影时自动关了灯,用户会觉得"这破系统又在自作主张"。系统在用户看电影时没关灯,用户会觉得"哦,可能没识别到",手动关一下就好。设计上应该偏保守:宁可漏报 20%,也不能误报超过 5%。

连续场景切换抖动:用户从客厅走到卧室,可能触发"影音→行走→休息"三次连续切换。UI 闪烁会让用户困惑。解决方案是引入最小场景停留时间——一旦切换到一个场景,至少停留 30 秒才能再次切换。

多成员家庭的冲突:爸爸在客厅看电影(影音模式),妈妈在卧室看书(阅读模式),系统应该按哪个场景?答案是按设备归属性——每个场景的操作限定在目标房间的设备范围内。客厅的影音模式不应该影响卧室的灯光。

五、总结

  • 场景自动化应该是无感切换,而非弹出模式选择对话框
  • 多传感器融合的置信度分级(auto/suggest/ignore)是场景识别的正确交互模型
  • 加权规则引擎优于深度学习模型,可解释性在场景识别中比准确率更重要
  • 误报(false positive)比漏报(false negative)更伤害用户信任
  • 连续切换需要 30 秒最小停留时间防止 UI 抖动
  • 历史模式加成(7 天同时段频次)可以提升 10% 的识别准确率
  • 场景操作应限定在目标房间范围内,避免多成员场景冲突
  • 赞(0)
    未经允许不得转载:171主机测评 » AI 驱动的家庭场景识别:自动切换影音、休息与工作模式界面
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址