Flutter三方库适配OpenHarmony【flutter_speech】— 语音识别结果的后处理
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net语音识别引擎返回的原始文本,往往不能直接用于业务场景。缺少标点、格式混乱、偶尔有错别字——这些问题都需要通过后处理来解决。flutter_speech本身不做后处理,它只负责把引擎返回的原始文本透传给Dart层。后处理的逻辑应该在Dart层实现,这样可以跨平台复用。今天我们来看看语音识别结
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
语音识别引擎返回的原始文本,往往不能直接用于业务场景。缺少标点、格式混乱、偶尔有错别字——这些问题都需要通过后处理来解决。
flutter_speech本身不做后处理,它只负责把引擎返回的原始文本透传给Dart层。后处理的逻辑应该在Dart层实现,这样可以跨平台复用。
今天我们来看看语音识别结果的常见后处理需求和实现方案。
一、识别文本的标点符号处理
1.1 Core Speech Kit的标点能力
好消息是,Core Speech Kit的中文识别结果通常自带标点:
用户说:"今天天气怎么样明天会下雨吗"
识别结果:"今天天气怎么样?明天会下雨吗?"
但标点的准确性不是100%,有时候会缺失或位置不对。
1.2 标点补全策略
如果识别结果缺少标点,可以用简单的规则补全:
String addPunctuation(String text) {
if (text.isEmpty) return text;
// 规则1:以疑问词结尾加问号
final questionWords = ['吗', '呢', '啊', '么', '什么', '怎么', '哪里', '谁', '几'];
for (final word in questionWords) {
if (text.endsWith(word) && !text.endsWith('?')) {
return '$text?';
}
}
// 规则2:以感叹词结尾加感叹号
final exclamationWords = ['啊', '呀', '哇', '太好了', '真棒'];
for (final word in exclamationWords) {
if (text.endsWith(word) && !text.endsWith('!')) {
return '$text!';
}
}
// 规则3:其他情况加句号
final lastChar = text[text.length - 1];
if (!['。', '?', '!', ',', '、', ';', ':'].contains(lastChar)) {
return '$text。';
}
return text;
}
1.3 标点处理的时机
| 时机 | 优点 | 缺点 |
|---|---|---|
| 实时处理(每次onSpeech) | 用户立即看到标点 | 部分结果的标点可能不准 |
| 最终处理(onRecognitionComplete) | 标点更准确 | 实时显示时没有标点 |
| 混合处理 | 平衡 | 实现稍复杂 |
推荐混合处理:实时显示时不加标点(保持原始结果),最终结果时补全标点。
_speech.setRecognitionResultHandler((String text) {
// 实时结果:原样显示
setState(() => transcription = text);
});
_speech.setRecognitionCompleteHandler((String text) {
// 最终结果:补全标点
setState(() => transcription = addPunctuation(text));
});
二、中英混合文本的格式化
2.1 常见的格式问题
Core Speech Kit识别中英混合文本时,可能出现格式问题:
| 输入 | 识别结果 | 问题 |
|---|---|---|
| “打开WiFi” | “打开wifi” | 大小写丢失 |
| “发送Email给他” | “发送email给他” | 大小写丢失 |
| “iPhone 15很好” | “iphone15很好” | 大小写+空格丢失 |
2.2 英文单词大小写修正
// 常见品牌名和缩写的正确大小写
final Map<String, String> _wordCaseMap = {
'wifi': 'WiFi',
'bluetooth': 'Bluetooth',
'iphone': 'iPhone',
'ipad': 'iPad',
'email': 'Email',
'app': 'App',
'ok': 'OK',
'ai': 'AI',
'api': 'API',
'url': 'URL',
'html': 'HTML',
'css': 'CSS',
'pdf': 'PDF',
};
String fixWordCase(String text) {
String result = text;
_wordCaseMap.forEach((lower, correct) {
// 用正则匹配完整单词(避免部分匹配)
result = result.replaceAllMapped(
RegExp('(?<![a-zA-Z])$lower(?![a-zA-Z])', caseSensitive: false),
(match) => correct,
);
});
return result;
}
2.3 中英文之间加空格
中英文之间加空格是一种常见的排版规范(称为"盘古之白"):
String addSpaceBetweenCnEn(String text) {
// 中文后面跟英文,加空格
text = text.replaceAllMapped(
RegExp(r'([\u4e00-\u9fa5])([a-zA-Z0-9])'),
(m) => '${m[1]} ${m[2]}',
);
// 英文后面跟中文,加空格
text = text.replaceAllMapped(
RegExp(r'([a-zA-Z0-9])([\u4e00-\u9fa5])'),
(m) => '${m[1]} ${m[2]}',
);
return text;
}
处理前后对比:
处理前:"打开wifi设置然后连接bluetooth"
处理后:"打开 WiFi 设置然后连接 Bluetooth"
三、识别置信度与结果筛选
3.1 Core Speech Kit的置信度
当前Core Speech Kit的SpeechRecognitionResult不包含置信度字段:
interface SpeechRecognitionResult {
result: string; // 识别文本
isLast: boolean; // 是否最终结果
// 没有confidence字段
}
这意味着我们无法直接判断识别结果的可靠程度。
3.2 间接评估置信度
虽然没有直接的置信度,可以通过一些启发式规则间接评估:
class RecognitionConfidence {
/// 评估识别结果的可信度(0.0-1.0)
static double estimate(String text, Duration duration) {
double confidence = 1.0;
// 规则1:文本太短可能是误识别
if (text.length < 2) confidence *= 0.5;
// 规则2:文本长度与时长不匹配
final charsPerSecond = text.length / duration.inSeconds;
if (charsPerSecond > 10) confidence *= 0.7; // 说话太快,可能有误
if (charsPerSecond < 1) confidence *= 0.7; // 说话太慢,可能有误
// 规则3:包含大量重复字符
if (_hasExcessiveRepetition(text)) confidence *= 0.5;
return confidence.clamp(0.0, 1.0);
}
static bool _hasExcessiveRepetition(String text) {
if (text.length < 4) return false;
for (int i = 0; i < text.length - 3; i++) {
if (text[i] == text[i + 1] && text[i + 1] == text[i + 2] && text[i + 2] == text[i + 3]) {
return true; // 4个连续相同字符
}
}
return false;
}
}
3.3 低置信度结果的处理
void onRecognitionComplete(String text) {
final confidence = RecognitionConfidence.estimate(text, _recognitionDuration);
if (confidence < 0.3) {
// 置信度太低,提示用户
setState(() {
transcription = text;
_showLowConfidenceWarning = true;
});
} else {
setState(() {
transcription = text;
_showLowConfidenceWarning = false;
});
}
}
四、文本纠错与智能补全
4.1 常见的识别错误类型
| 错误类型 | 示例 | 原因 |
|---|---|---|
| 同音字错误 | “在→再”、“的→地” | 语音相同,语义不同 |
| 近音字错误 | “是→十”、“四→死” | 发音相近 |
| 吞字 | “我想去北京” → “我想去京” | 说话太快 |
| 多字 | “你好” → “你好好” | 回声或重复 |
4.2 简单的纠错规则
String correctCommonErrors(String text) {
// 常见的同音字纠错(基于上下文)
final corrections = {
'在见': '再见',
'在说': '再说',
'在来': '再来',
'的话': '的话', // 保持不变
'在次': '再次',
'做后': '最后',
};
String result = text;
corrections.forEach((wrong, correct) {
result = result.replaceAll(wrong, correct);
});
return result;
}
4.3 基于词典的纠错
更高级的纠错需要词典支持:
class SpellChecker {
final Set<String> _dictionary = {};
SpellChecker() {
// 加载常用词词典
_loadDictionary();
}
String correct(String text) {
// 分词
final words = _segment(text);
// 对每个词检查是否在词典中
// 如果不在,查找最相似的词
return words.map((w) => _dictionary.contains(w) ? w : _findSimilar(w)).join();
}
String _findSimilar(String word) {
// 编辑距离最小的词典词
// ...
return word;
}
}
💡 实际建议:对于flutter_speech这样的轻量级插件,不建议在客户端做复杂的纠错。简单的规则纠错就够了。如果需要高质量的纠错,应该在服务端处理。
4.4 智能补全
在用户说到一半时,可以提供补全建议:
// 基于历史输入的补全
List<String> getSuggestions(String partial) {
return _history
.where((h) => h.startsWith(partial))
.take(3)
.toList();
}
五、识别结果的本地存储与历史管理
5.1 存储方案选择
| 方案 | 适用场景 | 数据量 | 复杂度 |
|---|---|---|---|
| SharedPreferences | 少量历史记录 | <100条 | 低 |
| 文件存储 | 中等数据量 | <1000条 | 中 |
| SQLite | 大量数据+查询 | 无限制 | 高 |
5.2 使用SharedPreferences存储
import 'package:shared_preferences/shared_preferences.dart';
class RecognitionHistory {
static const _key = 'speech_history';
static const _maxItems = 50;
static Future<void> save(String text) async {
final prefs = await SharedPreferences.getInstance();
final history = prefs.getStringList(_key) ?? [];
history.insert(0, '${DateTime.now().toIso8601String()}|$text');
// 限制历史记录数量
if (history.length > _maxItems) {
history.removeRange(_maxItems, history.length);
}
await prefs.setStringList(_key, history);
}
static Future<List<HistoryItem>> load() async {
final prefs = await SharedPreferences.getInstance();
final history = prefs.getStringList(_key) ?? [];
return history.map((item) {
final parts = item.split('|');
return HistoryItem(
time: DateTime.parse(parts[0]),
text: parts.sublist(1).join('|'),
);
}).toList();
}
static Future<void> clear() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_key);
}
}
class HistoryItem {
final DateTime time;
final String text;
HistoryItem({required this.time, required this.text});
}
5.3 在识别完成时自动保存
_speech.setRecognitionCompleteHandler((String text) {
setState(() {
transcription = text;
_isListening = false;
});
// 自动保存到历史记录
if (text.isNotEmpty) {
RecognitionHistory.save(text);
}
});
5.4 历史记录UI
Widget _buildHistoryList() {
return FutureBuilder<List<HistoryItem>>(
future: RecognitionHistory.load(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
final items = snapshot.data!;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.text, maxLines: 2, overflow: TextOverflow.ellipsis),
subtitle: Text(_formatTime(item.time)),
onTap: () => _copyToClipboard(item.text),
);
},
);
},
);
}
六、后处理流水线
6.1 统一的后处理管道
将所有后处理步骤串成一个管道:
class TextPostProcessor {
static String process(String raw) {
String result = raw;
// 1. 去除首尾空白
result = result.trim();
// 2. 修正英文大小写
result = fixWordCase(result);
// 3. 中英文加空格
result = addSpaceBetweenCnEn(result);
// 4. 常见纠错
result = correctCommonErrors(result);
// 5. 补全标点
result = addPunctuation(result);
return result;
}
}
6.2 在回调中使用
_speech.setRecognitionCompleteHandler((String text) {
final processed = TextPostProcessor.process(text);
setState(() {
transcription = processed;
_isListening = false;
});
RecognitionHistory.save(processed);
});
总结
本文讲解了语音识别结果的后处理方案:
- 标点处理:Core Speech Kit自带标点,必要时用规则补全
- 中英混合格式化:修正大小写、添加中英文间空格
- 置信度评估:通过启发式规则间接评估结果可靠性
- 文本纠错:简单规则纠错,复杂纠错交给服务端
- 历史管理:SharedPreferences存储识别历史
下一篇我们讲与其他HarmonyOS Kit的联动——语音识别与TTS、AI Framework等Kit的结合。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐



所有评论(0)