Flutter 개발시 IDE에서도 Release 모드로 실행하다 낭패본 사연

  • 제가 개발중인 고양이 집사를 위한 만보기앱에서 발생한 이슈입니다.
  • 이 앱에는 블루투스 기기를 선택하는 페이지가 있습니다.
  • SingleChildScrollView 가 전체 body를 감싸고 있고, 그 안에 LoadingOverlay를 두어서 배타적으로 동작해야 하는 행위들을 막아버립니다. 이때 사용한 LoadingOverlay를 패키지는 아래 링크에 해당합니다.
  • LoadingOverlay 내부에는 복잡한 위젯들의 조합으로 구성되어 있습니다.
  • 그런데 LoadingOverlay가 나타나야 하는 순간 아래와 같은 화면 깨짐이 발생하게 되었습니다.
    • 이미지1
  • 원래 원했던 실행화면은 아래와 같습니다.
    • 이미지2
  • 이러한 화면 깨짐은 “transform layer is constructed with an invalid matrix.” 에러와 함께 발생했는데요.
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.
  • 이슈의 실체를 구체적으로 기술하면, LoadingOverlay가 Stack을 이용해서 CircularProgressIndicator를 만들어 삽입 할 때 중앙정렬을 하지 못하는 상황인 것 인데요.
  • 원인은 LoadingOverlay 내부의 Stack 구성시에 자식위젯의 높이가 무한대로 지정되어 버려서 인 것으로 파악되었습니다.
  • 사실 이 이슈는 디버그모드에서 실행했다면 쉽게 찾아낼 수 있는 버그 였는데요, 왜냐하면 디버그모드에서는 해당상황에 대한 경고메시지와 함께 페이지 구성이 아예 되지 않기 때문입니다. 반면 릴리즈 모드에서는 우스꽝스럽게라도 화면이 만들어 집니다. 당연히 원인이 되는 경고메시지도 출력되지 않습니다.
  • 디버그모드를 무겁다는 이유로 간과한 결과, 상당한 기술적부채가 발생한 것이죠. 아울러 이런 상황을 애당초에 방지하기 위해서라도 빌더툴의 사용해야 겠다는 생각이 점점 드네요. flutterflow 같은 툴이 괜히 있는게 아닐듯 합니다. 이런 툴이 있으면 애당초에 하지 말아야 할 위젯의 조합이나 구성을 막아주지 않을까 싶네요.

Flutter 에서 Google ML Kit으로 아기사진 골라내기

지난번에 Google ML Kit for Flutter의 Face Detection을 이용하는 간단한 앱을 만들어 봤습니다.

오늘은 Image Labeling을 이용해서 아기사진만 골라내는 코드를 작성해 보았습니다. 지난번 코드에서 크게 달라지는 부분은 없습니다. FaceDetection 관련된 로직이 Image Labeling으로 바뀌는 정도 입니다.

자세한 정보는 아래 링크를 참고하세요.

주요 소스코드

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
위 광고를 클릭해주시면 코드가 나타납니다.
// .. 전략
// 간단하게 클래스 하나 정의하구요
class ImageObj {
  final String title;
  final Image image;
  ImageObj({
    required this.title,
    required this.image,
  });
}

class _MyHomePageState extends State<MyHomePage> {
// 스테이트 클래스 내부에 아래처럼 위에서 정의한 클래스와 
// 이미지 라벨링 용 오브젝트를 선언합니다.
  List<ImageObj> images = [];
  final imageLabeler =
        ImageLabeler(options: ImageLabelerOptions(confidenceThreshold: 0.5));
  
  Future<void> _doDetect() async {
    final ImagePicker picker = ImagePicker();
    // 이미지 선택 다이얼로그 띄웁니다
    List<XFile> imagesFromPicker = await picker.pickMultiImage();
    List<bool> hasBabyIndex = [];
    List<String> textLabels = [];
    List<double> confidences = [];
    images = [];
    for(XFile item in imagesFromPicker) {
      // 이미지 피커에서 사용하는 XFile 타입을 ML Kit에서 사용하는 InputImage 형태로 변경
        final List<ImageLabel> labels =
                await imageLabeler.processImage(InputImage.fromFilePath(item.path));
        bool hasBaby = false;
        for (ImageLabel label in labels) {
          // 아기 사진 레이블의 색인(index)이 421번 입니다.
          if (label.index == 421) {
            hasBaby = true;
          }
        }
        hasBabyIndex.add(hasBaby);
    }
    setState(() {
      int i = 0;
      for(XFile item in imagesFromPicker) {
        // XFile 타입을 이미지 뷰어에서 사용하는 Image 타입으로 변환하기 위해서
        // cross_file_image 패키지를 여기서 사용합니다.
        images.add(ImageObj(
            title: hasBabyIndex[i++] ? "Baby" : "No Baby",
            image: Image(image: XFileImage(item))));
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: images.length > 0 ? ListView.builder(
            itemCount: images.length,
            itemBuilder: (context, index) {
                  return ListTile(
                    leading: images[index].image,
                    title: Text(images[index].title),
                    onTap: () {
                    },
                );
        }) : Text("No Images"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _doDetect,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

수행결과

  • 이미지1
  • 그 전에 No Human으로 나왔던 사진과 사람이 아닌 사진을 따로 뽑아서 돌려봤습니다.
  • 보시다시피 이제는 Baby로 체크해주네요. 정확성이 얼굴인식보다는 더 높아 보입니다.
  • 다만 엄마와 함께 찍은 사진은 다른 레이블로 분류되고 있습니다.
    • 이럴때는 커스텀 모델을 만들거나 객체 감지 기술을 이용해야 할 것으로 보입니다.
  • 다음 시간에는 객체 감지 기술에 대해 알아보도록 하겠습니다.

Flutter 에서 Google ML Kit으로 얼굴인식 수행하기

오늘은 Flutter에서 다양한 유형의 사진을 다중선택 한 후 Google ML Kit의 얼굴인식 기능을 이용하여 사람이 사진 속에 존재하는지를 판정하는 간단한 앱을 만들어 보겠습니다.

사진 다중 선택은 예전에도 사용해봤던 아래 패키지를 이용하고자 합니다.

아울러서 이미지 타입을 용이하게 변환하고자 다음 패키지도 이용합니다.

주요 소스코드

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
위 광고를 클릭해주시면 코드가 나타납니다.
// .. 전략
// 간단하게 클래스 하나 정의하구요
class ImageObj {
  final String title;
  final Image image;
  ImageObj({
    required this.title,
    required this.image,
  });
}

class _MyHomePageState extends State<MyHomePage> {
// 스테이트 클래스 내부에 아래처럼 위에서 정의한 클래스와 
// 얼굴인식 용 오브젝트를 선언합니다.
  List<ImageObj> images = [];
  final FaceDetector _faceDetector = FaceDetector(
    options: FaceDetectorOptions(
      enableContours: true,
      enableClassification: true,
    ),
  );
  Future<void> _doDetect() async {
    final ImagePicker picker = ImagePicker();
    // 이미지 선택 다이얼로그 띄웁니다
    List<XFile> imagesFromPicker = await picker.pickMultiImage();
    List<int> faceCount = [];
    images = [];
    for(XFile item in imagesFromPicker) {
      // 이미지 피커에서 사용하는 XFile 타입을 ML Kit에서 사용하는 InputImage 형태로 변경
      final faces = await _faceDetector.processImage(InputImage.fromFilePath(item.path));
      faceCount.add(faces.length);
    }
    setState(() {
      int i = 0;
      for(XFile item in imagesFromPicker) {
        // XFile 타입을 이미지 뷰어에서 사용하는 Image 타입으로 변환하기 위해서
        // cross_file_image 패키지를 여기서 사용합니다.
        images.add(
          ImageObj(title: faceCount[i++] > 0 ? 
            "Human" : "No Human", image: Image(image: XFileImage(item))));
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: images.length > 0 ? ListView.builder(
            itemCount: images.length,
            itemBuilder: (context, index) {
                  return ListTile(
                    leading: images[index].image,
                    title: Text(images[index].title),
                    onTap: () {
                    },
                );
        }) : Text("No Images"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _doDetect,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

수행결과

  • 이미지1
  • 이미지2
  • 사람이 아닌 것은 확실히 “아니라고” 합니다. 정확히 표현하면 사람 얼굴을 0개 찾아냅니다.
    • 빵, 꽃, 고양이 사진을 선택했는데 모두 아니라고 나옵니다.
  • 그런데 사람의 경우에는 정면사진이 아닐 경우 오인식이 나타나는군요.
    • 아무래도 ML 모델의 원리가 눈도 2쌍, 귀도 2쌍이어야 하고
    • 동공이 측정되어야하고 이목구비의 거리 및 비율 등을 특징값으로 학습한 것이 아닐까 추측됩니다.
    • 옆 모습이나, 눈감은 모습, 누워있는 모습은 얼굴이 0개로 측정되는 경우가 있습니다.