Flutter 多模态识图¶
使用多模态模型让 AI 理解图片内容,支持 OCR 文字提取、图像描述、图片问答等。
功能预览¶

左侧导航选择 👁️ 多模态识图 → 上传图片 → 输入查询指令 → 获取 AI 分析结果
目录¶
基础概念¶
多模态模型支持在 messages 中同时发送文本和图像:
{
'model': 'gpt-4o-mini',
'messages': [
{
'role': 'user',
'content': [
{'type': 'text', 'text': '这张图片里有什么?'},
{
'type': 'image_url',
'image_url': {
'url': 'https://example.com/image.jpg'
}
}
]
}
]
}
支持两种图像输入方式:
- 图像 URL:公网可访问的图片地址
- Base64 Data URI:
data:image/png;base64,xxxxxx(本地图片)
常用多模态模型:
gpt-4o-mini/gpt-4o- OpenAI 视觉模型doubao-1-5-vision-pro-32k-250115- 中文视觉更强qwen2.5vl-72b-instruct- 开源多模态模型
本地图片转 Base64¶
import 'dart:io';
import 'dart:convert';
import 'package:image_picker/image_picker.dart';
Future<String?> pickAndEncode() async {
final picker = ImagePicker();
final XFile? file = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 80,
);
if (file == null) return null;
final bytes = await file.readAsBytes();
final base64Str = base64.encode(bytes);
final mimeType = file.path.toLowerCase().endsWith('.png')
? 'image/png'
: 'image/jpeg';
return 'data:$mimeType;base64,$base64Str';
}
使用图像 URL¶
Future<String> describeImage(String imageUrl) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'gpt-4o-mini',
'messages': [
{
'role': 'user',
'content': [
{'type': 'text', 'text': '请用中文详细描述这张图片的内容。'},
{
'type': 'image_url',
'image_url': {'url': imageUrl}
}
]
}
],
}),
);
final data = json.decode(response.body);
return data['choices'][0]['message']['content'];
}
图片描述 / OCR¶
使用 Base64 本地图片做 OCR 文字提取:
Future<String> extractTextFromImage(String base64Image) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'gpt-4o-mini',
'messages': [
{
'role': 'user',
'content': [
{
'type': 'text',
'text': '请提取图片中的所有文字,按段落输出。如果图片中没有文字,请回复"未检测到文字"。'
},
{
'type': 'image_url',
'image_url': {'url': base64Image}
}
]
}
],
}),
);
final data = json.decode(response.body);
return data['choices'][0]['message']['content'];
}
图片问答¶
根据图片内容回答用户问题:
Future<String> askAboutImage(String question, String base64Image) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'gpt-4o-mini',
'messages': [
{
'role': 'user',
'content': [
{'type': 'text', 'text': question},
{
'type': 'image_url',
'image_url': {'url': base64Image}
}
]
}
],
}),
);
final data = json.decode(response.body);
return data['choices'][0]['message']['content'];
}
完整示例¶
在 Flutter 应用中集成图片理解:
class VisionPage extends StatefulWidget {
const VisionPage({super.key});
@override
State<VisionPage> createState() => _VisionPageState();
}
class _VisionPageState extends State<VisionPage> {
String? _imagePath;
String? _base64Image;
final _questionController = TextEditingController(
text: '请描述这张图片的主要内容',
);
String _result = '';
bool _loading = false;
Future<void> _pickImage() async {
final picker = ImagePicker();
final file = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 80,
);
if (file == null) return;
final bytes = await file.readAsBytes();
final b64 = base64.encode(bytes);
final mimeType = file.path.toLowerCase().endsWith('.png')
? 'image/png'
: 'image/jpeg';
setState(() {
_imagePath = file.path;
_base64Image = 'data:$mimeType;base64,$b64';
});
}
Future<void> _ask() async {
if (_base64Image == null || _loading) return;
setState(() => _loading = true);
try {
final reply = await askAboutImage(
_questionController.text,
_base64Image!,
);
setState(() => _result = reply);
} catch (e) {
setState(() => _result = '请求失败: $e');
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('多模态识图')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
onPressed: _pickImage,
child: const Text('选择图片'),
),
if (_imagePath != null) ...[
const SizedBox(height: 12),
Image.file(
File(_imagePath!),
height: 200,
fit: BoxFit.cover,
),
],
const SizedBox(height: 16),
TextField(
controller: _questionController,
decoration: const InputDecoration(
labelText: '你想了解这张图片的什么内容?',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _base64Image == null || _loading ? null : _ask,
child: Text(_loading ? '分析中...' : '分析图片'),
),
const SizedBox(height: 16),
if (_result.isNotEmpty)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
),
child: Text(_result),
),
],
),
),
);
}
}