본문 바로가기

06. 앱

00018. [APP-00002] 사진 기반 식물/동물 인식 앱 개발기 #9

반응형

#9. 실시간 인식 구현하기 (캡처 + 전송 + 결과 표시)

이번 편에서는 실시간 인식을 위한 핵심인 주기적인 이미지 캡처 → 전송 → 결과 표시를 구현해볼게. 초반에는 5초 간격으로 테스트해보는 게 좋아.


🔄 1단계: 이미지 주기적 캡처 타이머 설정

realtime_screen.dart에 타이머와 이미지 인식 로직 추가:

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

import '../services/plant_id_service.dart';
import '../services/image_encoder.dart';

class RealtimeScreen extends StatefulWidget {
  const RealtimeScreen({super.key});

  @override
  State<RealtimeScreen> createState() => _RealtimeScreenState();
}

class _RealtimeScreenState extends State<RealtimeScreen> {
  CameraController? _controller;
  List<CameraDescription>? _cameras;
  Timer? _timer;
  String? _result;

  @override
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    _cameras = await availableCameras();
    _controller = CameraController(_cameras![0], ResolutionPreset.medium);
    await _controller!.initialize();
    setState(() {});
    _startTimer();
  }

  void _startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 5), (_) => _captureAndIdentify());
  }

  Future<void> _captureAndIdentify() async {
    if (!_controller!.value.isInitialized || _controller!.value.isTakingPicture) return;

    final tempDir = await getTemporaryDirectory();
    final imagePath = '${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
    final file = await _controller!.takePicture();
    final imageFile = File(file.path);

    final base64Image = await encodeImageToBase64(imageFile);
    try {
      final result = await identifyPlant(base64Image);
      final name = result['suggestions']?[0]?['plant_name'] ?? 'Unknown';
      setState(() => _result = name);
    } catch (e) {
      setState(() => _result = 'Error');
    }
  }

  @override
  void dispose() {
    _timer?.cancel();
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(body: Center(child: CircularProgressIndicator()));
    }

    return Scaffold(
      appBar: AppBar(title: const Text('실시간 식물 인식')),
      body: Stack(
        children: [
          CameraPreview(_controller!),
          if (_result != null)
            Positioned(
              bottom: 32,
              left: 16,
              right: 16,
              child: Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.black.withOpacity(0.6),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Text(
                  '인식 결과: $_result',
                  style: const TextStyle(color: Colors.white, fontSize: 16),
                  textAlign: TextAlign.center,
                ),
              ),
            ),
        ],
      ),
    );
  }
}

✅ 작동 흐름

  • 카메라가 켜지고 5초마다 사진 1장 캡처
  • 서버로 전송하여 식물 이름 분석
  • 인식된 이름을 화면 아래쪽에 표시

🧪 팁 & 개선 방향

  • 일정 거리 이상 이동하거나, 프레임 차이 클 때만 캡처하도록 개선 가능
  • 사용자가 “중지/시작” 버튼을 누를 수 있게 인터페이스 추가할 수도 있음
  • 실시간 오버레이 개선: 인식 정확도, 유사 이미지도 함께 표시 가능

다음 에피소드 예고

#10. 앱 완성 및 배포 준비

  • 앱 아이콘/스플래시 화면 설정
  • Android/iOS 빌드 및 테스트
  • 블로그 시리즈 정리 및 깃허브 공유 준비
반응형