Camera ํจํค์ง์ Google ML Kit์ ์ฌ์ฉํ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ํ๋ ์ ๋ถํ ๋ฐ ์ฒ๋ฆฌ ํด๋์ค
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์นด๋ฉ๋ผ๋ก๋ถํฐ ์ค์๊ฐ์ผ๋ก ํ๋ ์์ ์์ ํ๊ณ , ๋ด๋ถ ํ๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ํจ์จ์ ์ธ ์คํธ๋ฆผ ํ๋ก์ธ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
- โ ์ค์๊ฐ ์นด๋ฉ๋ผ ์คํธ๋ฆฌ๋ฐ: Camera ํจํค์ง๋ฅผ ์ฌ์ฉํ ํ๋ ์ ์บก์ฒ
- โ ์ ๋ ฅ ํ ๊ด๋ฆฌ: ํ๋ ์์ ํ์ ์ ์ฅํ๊ณ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ
- โ ML Kit ํตํฉ: Google ML Kit InputImage ์๋ ๋ณํ
- โ ๋น๋๊ธฐ ํ๋ ์ ์ฒ๋ฆฌ: ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ๋ ์ ์ฒ๋ฆฌ
- โ ํ ์ค๋ฒํ๋ก์ฐ ๋ฐฉ์ง: ์ต๋ ํ ํฌ๊ธฐ ์ค์ ๋ฐ ์๋ ํ๋ ์ ๋๋กญ
- โ ์ผ์์ ์ง/์ฌ๊ฐ: ์คํธ๋ฆผ ์ ์ด ๊ธฐ๋ฅ
- โ ํต๊ณ ๋ชจ๋ํฐ๋ง: FPS, ์ฒ๋ฆฌ๋ ํ๋ ์, ๋๋กญ๋ ํ๋ ์ ๋ฑ
- โ ์๋ฌ ํธ๋ค๋ง: ๋ณ๋์ ์๋ฌ ์คํธ๋ฆผ ์ ๊ณต
- โ ์ปค์คํฐ๋ง์ด์ง: ์ฌ์ฉ์ ์ ์ ํ๋ ์ ์ฒ๋ฆฌ ํจ์ ์ง์
pubspec.yaml์ ๋ค์ ์์กด์ฑ์ ์ถ๊ฐํ์ธ์:
dependencies:
camera: ^0.10.5+7
google_mlkit_commons: ^0.6.0
image: ^4.1.3import 'package:realtime_stream_processor/realtime_stream_processor.dart';
// ํ๋ก์ธ์ ์์ฑ
final processor = RealtimeStreamProcessor(
maxQueueSize: 10, // ์ต๋ ํ ํฌ๊ธฐ
);
// ์นด๋ฉ๋ผ ์ด๊ธฐํ
await processor.initializeCamera(
resolutionPreset: ResolutionPreset.medium,
);
// ์คํธ๋ฆฌ๋ฐ ์์
await processor.startStreaming();
// ๊ฒฐ๊ณผ ์์
processor.outputStream.listen((result) {
print('Processed frame ${result.frameId}');
print('Metadata: ${result.metadata}');
// ML Kit InputImage ์ฌ์ฉ
if (result.mlKitInputImage != null) {
// ML Kit ๋ชจ๋ธ๋ก ์ฒ๋ฆฌ
// await faceDetector.processImage(result.mlKitInputImage!);
}
});
// ์๋ฌ ์ฒ๋ฆฌ
processor.errorStream.listen((error) {
print('Error: $error');
});
// ์คํธ๋ฆฌ๋ฐ ์ค์ง
await processor.stopStreaming();
// ๋ฆฌ์์ค ์ ๋ฆฌ
await processor.dispose();final processor = RealtimeStreamProcessor(
maxQueueSize: 10,
onProcessFrame: (frameData) async {
// ์ปค์คํ
์ฒ๋ฆฌ ๋ก์ง
final cameraImage = frameData.cameraImage;
// ์: ์ผ๊ตด ์ธ์, ๊ฐ์ฒด ๊ฐ์ง, ์ธ๊ทธ๋ฉํ
์ด์
๋ฑ
// final faces = await faceDetector.processImage(inputImage);
return ProcessedFrameResult(
frameId: frameData.frameId,
processedAt: DateTime.now(),
metadata: {
'width': cameraImage.width,
'height': cameraImage.height,
'customData': 'your_data',
},
);
},
);// ์ผ์์ ์ง
processor.pause();
// ์ฌ๊ฐ
processor.resume();
// ์ค์ง
await processor.stopStreaming();
// ํต๊ณ ํ์ธ
final stats = processor.statistics;
print('FPS: ${stats['fps']}');
print('Processed: ${stats['processedFrames']}');
print('Dropped: ${stats['droppedFrames']}');โโโโโโโโโโโโโโโ
โ Camera โ
โโโโโโโโฌโโโโโโโ
โ Stream
โผ
โโโโโโโโโโโโโโโ
โ Input Queue โ โโโ maxQueueSize๋ก ์ ํ
โโโโโโโโฌโโโโโโโ
โ Pop
โผ
โโโโโโโโโโโโโโโ
โ Process โ โโโ onProcessFrame ์ฝ๋ฐฑ
โ Frame โ
โโโโโโโโฌโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโ
โ Output โ
โ Stream โ โโโบ outputStream.listen()
โโโโโโโโโโโโโโโ
์ฃผ์ ํ๋ก์ธ์ ํด๋์ค
RealtimeStreamProcessor({
int maxQueueSize = 10,
Future<ProcessedFrameResult> Function(FrameData)? onProcessFrame,
})| ๋ฉ์๋ | ์ค๋ช |
|---|---|
initializeCamera() |
์นด๋ฉ๋ผ ์ด๊ธฐํ |
startStreaming() |
์คํธ๋ฆฌ๋ฐ ์์ |
stopStreaming() |
์คํธ๋ฆฌ๋ฐ ์ค์ง |
pause() |
์ผ์์ ์ง |
resume() |
์ฌ๊ฐ |
dispose() |
๋ฆฌ์์ค ์ ๋ฆฌ |
enqueueFrame() |
์๋ ํ๋ ์ ์ถ๊ฐ (ํ ์คํธ์ฉ) |
| ์์ฑ | ํ์ | ์ค๋ช |
|---|---|---|
outputStream |
Stream<ProcessedFrameResult> |
์ฒ๋ฆฌ๋ ํ๋ ์ ์คํธ๋ฆผ |
errorStream |
Stream<Exception> |
์๋ฌ ์คํธ๋ฆผ |
queueSize |
int |
ํ์ฌ ํ ํฌ๊ธฐ |
isProcessing |
bool |
์ฒ๋ฆฌ ์ค ์ฌ๋ถ |
isPaused |
bool |
์ผ์์ ์ง ์ํ |
statistics |
Map<String, dynamic> |
ํต๊ณ ์ ๋ณด |
์ ๋ ฅ ํ๋ ์ ๋ฐ์ดํฐ
class FrameData {
final CameraImage cameraImage;
final DateTime timestamp;
final int frameId;
}์ฒ๋ฆฌ๋ ํ๋ ์ ๊ฒฐ๊ณผ
class ProcessedFrameResult {
final int frameId;
final DateTime processedAt;
final Uint8List? processedImageBytes;
final Map<String, dynamic> metadata;
final InputImage? mlKitInputImage;
}import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
final faceDetector = FaceDetector(
options: FaceDetectorOptions(enableLandmarks: true),
);
final processor = RealtimeStreamProcessor(
onProcessFrame: (frameData) async {
final inputImage = _convertToInputImage(frameData.cameraImage);
final faces = await faceDetector.processImage(inputImage);
return ProcessedFrameResult(
frameId: frameData.frameId,
processedAt: DateTime.now(),
metadata: {
'faceCount': faces.length,
'faces': faces.map((f) => f.boundingBox).toList(),
},
mlKitInputImage: inputImage,
);
},
);import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
final textRecognizer = TextRecognizer();
final processor = RealtimeStreamProcessor(
onProcessFrame: (frameData) async {
final inputImage = _convertToInputImage(frameData.cameraImage);
final recognizedText = await textRecognizer.processImage(inputImage);
return ProcessedFrameResult(
frameId: frameData.frameId,
processedAt: DateTime.now(),
metadata: {
'text': recognizedText.text,
'blocks': recognizedText.blocks.length,
},
mlKitInputImage: inputImage,
);
},
);import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
final objectDetector = ObjectDetector(
options: ObjectDetectorOptions(mode: DetectionMode.stream),
);
final processor = RealtimeStreamProcessor(
onProcessFrame: (frameData) async {
final inputImage = _convertToInputImage(frameData.cameraImage);
final objects = await objectDetector.processImage(inputImage);
return ProcessedFrameResult(
frameId: frameData.frameId,
processedAt: DateTime.now(),
metadata: {
'objectCount': objects.length,
'objects': objects.map((o) => o.labels).toList(),
},
mlKitInputImage: inputImage,
);
},
);await processor.initializeCamera(
cameraDescription: cameras[0], // ํน์ ์นด๋ฉ๋ผ ์ ํ
resolutionPreset: ResolutionPreset.high, // ํด์๋
enableAudio: false, // ์ค๋์ค ๋นํ์ฑํ
);// ํ ํฌ๊ธฐ๊ฐ ํด์๋ก ๋ ๋ง์ ํ๋ ์์ ๋ฒํผ๋ง
// ํ์ง๋ง ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐ ๋ฐ ์ง์ฐ ์๊ฐ ์ฆ๊ฐ
final processor = RealtimeStreamProcessor(
maxQueueSize: 20, // ๊ธฐ๋ณธ๊ฐ: 10
);// ๋ฎ์ ํด์๋ = ๋น ๋ฅธ ์ฒ๋ฆฌ
ResolutionPreset.low // 352x288
ResolutionPreset.medium // 720x480
ResolutionPreset.high // 1280x720
ResolutionPreset.veryHigh // 1920x1080- ์์ ํ (5-10): ๋ฎ์ ์ง์ฐ, ๋ ๋ง์ ํ๋ ์ ๋๋กญ
- ํฐ ํ (20-30): ๋์ ์ง์ฐ, ์ ์ ํ๋ ์ ๋๋กญ
processor.outputStream.listen((result) {
final processingTime = result.processedAt.difference(
result.metadata['timestamp'] as DateTime,
);
print('Processing time: ${processingTime.inMilliseconds}ms');
});// ๊ถํ ํ์ธ
// AndroidManifest.xml์ ์ถ๊ฐ
<uses-permission android:name="android.permission.CAMERA"/>
// iOS Info.plist์ ์ถ๊ฐ
<key>NSCameraUsageDescription</key>
<string>์นด๋ฉ๋ผ ์ ๊ทผ์ด ํ์ํฉ๋๋ค</string>// 1. ํด์๋ ๋ฎ์ถ๊ธฐ
ResolutionPreset.low
// 2. ํ ํฌ๊ธฐ ์ฆ๊ฐ
maxQueueSize: 20
// 3. ์ฒ๋ฆฌ ๋ก์ง ์ต์ ํ
// ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ isolate๋ก ๋ถ๋ฆฌ// 1. ํ ํฌ๊ธฐ ๊ฐ์
maxQueueSize: 5
// 2. ์ ๊ธฐ์ ์ผ๋ก dispose() ํธ์ถ
await processor.dispose();
processor = RealtimeStreamProcessor(...);MIT License
GenSpark AI Developer
- Flutter Camera Package
- Google ML Kit
- Dart Image Package