PoseNet 모델을 사용하여 Android에서 사람의 포즈 추정을 위한 TensorFlow Lite 샘플 애플리케이션을 발표했습니다. PoseNet은 주요 인체 부위의 위치를 감지하여 이미지나 동영상으로 부터 사람의 포즈를 추정하는 비전 모델입니다. 한 예로서, 이 모델은 이미지에서 사람의 팔꿈치 및/또는 무릎 위치를 추정할 수 있습니다. 포즈 추정 모델은 이미지에 나와 있는 사람이 누군지 식별하는 것이 아니라, 주요 인체 부위의 위치만 식별합니다.
TensorFlow Lite는 디바이스의 카메라를 활용하여 사람의 주요 인체 부위를 실시간으로 감지하고 표시하는 Android 샘플 애플리케이션을 제공합니다. 소스 코드를 확인하세요!

이 모델이 흥미로운 이유는?

그것은 포즈 추정을 통해 수많은 어플리케이션의 가능성이 열리기 때문입니다. 몇 가지만 꼽자면, 개발자가 인체 이미지를 기반으로 하고, 으로 표현하고, 스포츠 선수의 특징적인 걸음걸이를 분석할 수 있습니다. Google I/O’19에서 TensorFlow Lite는 사용자가 PoseNet 모델을 사용하여 춤추는 방법을 배우는 데 도움이 되는 앱인 Dance Like를 선보인 바 있습니다.
앱 개발자와 머신러닝 전문가는 이 샘플 애플리케이션을 통해 가볍게 작동하는 모바일 모델의 가능성을 더욱 수월하게 탐구할 수 있을 것입니다.

PoseNet 샘플 애플리케이션

자바로 작성된 기존의 Android 예제와는 반대로, PoseNet 샘플 앱은 Kotlin으로 개발되었습니다. 이 앱의 개발 목표는 누구든 최소한의 오버헤드로 PoseNet 모델을 쉽게 사용할 수 있도록 하는 것입니다. 이 샘플 앱은 모델의 입출력에 대한 전후 처리를 담당하는  PoseNet 라이브러리를 포함합니다. 아래 다이어그램은 애플리케이션, PoseNet 라이브러리, TensorFlow Lite 라이브러리 간의 워크플로를 나타낸 것입니다.
PoseNet 앱 워크플로

PoseNet 라이브러리

PoseNet 라이브러리는 처리된 카메라 이미지를 선택하고 사람의 주요 인체 부위의 위치에 관한 정보를 반환하는 인터페이스를 제공합니다. 이 기능은 처리된 RGB 비트맵에서 TensorFlow Lite 인터프리터를 실행하고 Person 객체를 반환하는 메서드인 estimateSinglePose()에 의해 제공됩니다. 이 페이지에서는 PoseNet의 입력과 출력을 해석하는 방법을 설명합니다.
Person 클래스는 주요 인체 부위의 위치와 그와 관련된 신뢰도 점수를 함께 포함합니다. 어떤 사람의 신뢰도 점수는 각 주요 지점의 신뢰도 점수 평균으로, 이는 그 위치에 주요 지점이 존재할 확률을 나타냅니다.


각각의 KeyPoint는 특정 BodyPartPosition과 그 주요 지점의 신뢰도 점수에 관한 정보를 가지고 있습니다. 정의된 모든 주요 지점의 목록은 여기에서 액세스할 수 있습니다.


PoseNet 샘플 앱

PoseNet 샘플 앱은 카메라에서 프레임을 캡쳐하고 이미지에 주요 지점을 실시간으로 오버레이하는 온디바이스 카메라 앱입니다.
이 애플리케이션은 각각의 수신 카메라 이미지에 대해 다음 단계를 수행합니다.
  1. 카메라 미리보기에서 이미지 데이터를 캡처하여 YUV_420_888에서 ARGB_888 형식으로 변환합니다.
  2. RGB 형식의 프레임 데이터에서 픽셀을 보유할 Bitmap 객체를 만듭니다. 모델로 전달할 수 있도록 Bitmap을 모델 입력 크기에 맞게 자르고 크기를 조정합니다.
  3. PoseNet 라이브러리에서 estimateSinglePose() 함수를 호출하여 Person 객체를 가져옵니다.
  4. Bitmap의 크기를 화면 크기에 맞춰 다시 조정합니다. Canvas 객체에 새 Bitmap을 그립니다.
  5. Person 객체에서 얻은 주요 지점의 위치를 사용하여 캔버스에 골격을 그립니다. 기본적으로 0.2로 설정된 임계값 이상의 신뢰도 점수를 가진 주요 지점을 표시합니다.
  6. 포즈 렌더링을 카메라 프레임과 동기화하기 위해, 포즈와 카메라에 대해 별도의 View 인스턴스를 사용하는 대신 출력 표시를 위해 단일 SurfaceView를 사용했습니다. SurfaceView는 View 캔버스에서 가져오기, 잠금 및 그리기를 통해 지연 없이 화면에 표면을 배치하는 작업을 처리합니다.


온디바이스 실행

GitHub에서 소스 코드를 다운로드하고 실행 방법에 관한 안내는 README를 참조하여 앱을 사용해 보실 것을 권해 드립니다.

로드맵에서

우리는 앞으로 다음을 비롯하여 이 샘플 앱에 대해 더 많은 기능을 탐구해보고자 합니다.
  1. 다중 포즈 추정
  2. GPU delegating를 사용한 GPU 가속
  3. NNAPI delegating를 사용한 NNAPI 가속
  4. 모델의 훈련 후 양자화로 지연 시간 감소
  5. ResNet PoseNet 모델과 같은 추가 모델 옵션

올 여름 PoseNet 샘플 앱을 개발하는 과정은 정말 즐거운 일이었습니다! 이 앱을 통해 온디바이스 머신러닝에 더욱 쉽게 접근할 수 있기를 바랍니다. 이 앱을 사용하실 경우 #TFLite, #TensorFlow, #PoweredByTF를 사용해 우리와 공유해 주세요.

감사의 말


Tensorflow Lite 소프트웨어 엔지니어로서 우리를 이끌어준 Nupur GargPulkit Bhuwalka, PoseNet 모델을 만든 Tyler Zhu, 동료 인턴인 Pavel Senchanka, Pixel Camera 소프트웨어 엔지니어인 Clément Julliard 그리고 TensorFlow Lite 팀에 특별히 감사드립니다.


각 사용자의 창의적 프로세스를 맞춤설정하고 안내하기 위한 도구로서 머신러닝을 사용합니다.
VSCO는 사용자들이 구식 필름 카메라로 찍은 영상의 에뮬레이션을 포함한 다양한 이미지를 변환하는 데 도움이 되도록 사전 구성된 편집 조정 기능인 160여 가지 프리셋 카탈로그를 보유하고 있습니다.


필터를 적용하기 전의 건물 사진. <Sarah Hollander가 촬영한 이미지(왼쪽). AU5 프리셋을 적용한 건물 사진.(오른쪽)>

하지만 우리의 연구에서 시사하는 바에 따르면, 회원들은 프리셋의 수가 너무 많아 충분히 활용해 볼 엄두를 내지 못하고 결국엔 새로운 프리셋을 시도해 보지 않고 익히 알고 있고 좋아하는 극소수의 프리셋만 사용하는 것으로 나타났습니다.


해결 방법

사용자가 창의성을 발휘해 이미지를 편집할 여지는 그대로 남겨두면서도 믿을 수 있는 안내를 제공하고 새로운 발견에 나서도록 격려함으로써 결정 피로를 극복하는 것이 우리의 해결 과제였습니다. 우리의 이미징 팀은 다양한 유형의 이미지를 보완해주는 프리셋을 신중하게 큐레이팅하여 사진마다 맞춤설정된 권장 사항을 제시할 수 있도록 했습니다. 깊은 합성곱 신경망(CNN) 모델을 사용하는 온디바이스 머신러닝으로 이미지에 알맞은 프리셋을 추천함으로써 이 문제를 해결하기로 했습니다. CNN 모델은 이미지에 포함된 수많은 뉘앙스를 파악하여 기존의 컴퓨터 비전 알고리즘보다 쉽고 빠르게 범주화할 수 있기 때문입니다.
이 모든 점을 염두에 두고서 온디바이스 머신러닝을 사용하여 어떤 종류의 사진을 편집 중인지 파악한 다음 큐레이팅된 목록에서 적절한 프리셋을 추천하는 'For This Photo' 기능을 개발했습니다. 'For This Photo' 기능은 사용자들의 뜨거운 호응을 받아, 지금은 모든 프리셋을 보여주는 'All' 기능에 이어 두 번째로 많이 사용되는 카테고리가 되었습니다.


'For This Photo' 기능 사용 안내 동영상

For This Photo' 기능을 파악하기 위해 사용자가 이미지 편집을 시작할 때부터 동작 흐름을 단계별로   살펴보겠습니다. 사용자가 Edit View에서 이미지를 여는 즉시 모델을 통한 추론을 시작하고, 이미지에 적합한 카테고리를 구합니다. 그런 다음, 카테고리 ID와 일치하는 ID를 캐시된 카탈로그에서 찾아서 그 카테고리에 대한 프리셋 목록을 검색합니다. VSCO 멤버십 기능의 일부인 유료 프리셋과 무료 프리셋을 조합한 6가지 프리셋을 선택하여 'For This Photo' 섹션에 표시합니다. 비회원에게 멤버십의 가치를 알리기 위해 무료 및 유료 프리셋을 모두 표시하는 것이 우리에겐 중요했습니다. 즉, VSCO 회원으로서 누릴 수 있는 가치를 구체적인 상황에서 제시하기 위해, 비회원이 VSCO 멤버십에 속한 프리셋을 미리 볼 수 있도록 했습니다. 회원으로서는 이미지의 유형에 따라 사용 가능한 프리셋을 습득할 수 있는 이점이 있습니다.


온디바이스 ML로 접근성, 속도, 프라이버시 보장

이 기능을 구현하기 위해 서버 기반 머신러닝을 사용할 수 없다는 점을 처음부터 알고 있었습니다. 이 기능이 온디바이스 ML을 사용하도록 하고 싶었던 데는 오프라인 편집, 속도, 프라이버시 보호라는 세 가지 주된 이유가 있었습니다.
첫째, 회원이 온라인 상태일 때만 이 기능을 제공함으로써 회원의 창의성을 제한하고 싶지 않았습니다. 영감은 어디서든 떠오를 수 있습니다. 사막 한가운데나 산꼭대기와 같은 곳처럼, 네트워크 연결이 제한되는 공간에 있더라도 즉시 사진을 찍고 편집할 수 있어야 한다고 생각했습니다. 또한 우리 사용자층의 큰 비중을 차지하는 사용자들은 미국 이외의 지역에 살고 있으므로, 모든 이가 늘 고속 인터넷에 액세스할 수 있는 환경을 누리는 것은 아닙니다.
둘째, 빠르게 편집할 수 있도록 하고 싶었습니다. 만약 우리가 클라우드에서 ML 모델을 사용하여 'For This Photo' 기능을 제공했다면, 우리가 사용자의 이미지를 업로드하여 분류하고(시간, 대역폭, 데이터가 소요됨) 사용자가 프리셋을 다운로드해야 할 것이므로, 연결 속도가 빠른 환경에서도 그 처리 과정이 느릴 것이며 연결 속도가 느린 환경에서는 아예 불가능할 수도 있을 것입니다. 반면에, 온디바이스 ML 수행은 모든 과정이 기기의 로컬 위치에서 빠르게 이루어지고 네트워크에 연결할 필요도 없음을 의미합니다. 이는 사용자가 찰나의 순간을 포착하고 오로지 창작에만 몰입하도록 돕는 데 결정적으로 중요한 사항입니다.
셋째, 편집 프로세스가 비공개로 이루어집니다. 서버 측 솔루션은 사용자가 사진을 게시하기 전  사진을 여전히 편집하고 있는 동안에도 사용자 사진의 업로드를 받아야 합니다. 창작 과정에서 사용자의 프라이버시를 안전하게 지켜주고 싶었습니다.

TensorFlow를 선택하는 이유

맞춤 모델로 온디바이스 머신러닝을 수행하려고 했다는 점을 생각해 본다면, 서버에서 모델을 쉽게 훈련할 수 있다는 점과 훈련한 모델을 TFLiteConverter를 사용하여 스마트폰에서 호환되는 모델(.tflite 형식)로 변환할 수 있다는 점에서 TensorFlow Lite를 선택하는 것이 당연한 일이었습니다.
또한 서버 측 ML을 위해 프로덕션 시스템에서 TensorFlow와 TensorFlow Serving의 성공을 이미 경험한 바 있었습니다. TensorFlow의 라이브러리는 프로덕션 환경에서의 ML 실행을 주요 포커스로 삼아 디자인된 라이브러리이므로, TensorFlow Lite 역시 다를 바 없을 것이라 여겼습니다.
ML Kit를 사용해 TensorFlow Lite 모델에서 수월하게 추론을 실행하여 우리의 앱에 매우 완벽하게 통합했습니다. 이를 통해 이 기능을 프로토타입 단계에서 프로덕션 준비 완료 단계까지 무척 빠르게 진행할 수 있었습니다. ML Kit는 우리가 저수준의 TensorFlow Lite C++ 라이브러리를 직접 다룰 필요 없이 모델을 초기화 및 로드할 뿐 아니라 이미지에 대한 추론을 실행할 수 있도록 고수준 API를 제공했으며, 그 덕분에 개발 프로세스를 훨씬 더 빠르게 진행함으로써  모델을 더욱 정교하게 가다듬는 데 더 많은 시간을 할애할 수 있었습니다.


VSCO의 머신러닝 스택 개요

ML 스택의 경우, 이미지에 대한 딥 러닝에는 TensorFlow를 사용하고 동작 데이터에 대한 얕은 학습에는 Apache Spark를 사용합니다.
프로덕션 단계에서는 다양한 컨벌루션 신경망을 통해 VSCO로 업로드되는 모든 이미지를 실시간으로 실행하는 TensorFlow Serving을 사용하는 실시간 클라우드 기반 추론 파이프라인이 있습니다. 이런 모델의 추론 결과는 Related Images, Search, Just For You 그리고 Discover의 다른 섹션 등, 다양한 제품 기능에 사용됩니다. 온디바이스 머신러닝을 위해 ML Kit 및 TensorFlow Lite와 같이, TensorFlow 생태계의 모바일 친화적인 솔루션을 사용하는데, 우리가 구현한 스택의 이 부분에 관한 자세한 내용은 다음 섹션에서 살펴보겠습니다.
Related Images Just For You
Search User Suggestions


이미지 메타데이터, 동작 이벤트, 관계형 데이터 등의 데이터 저장소의 다양한 소스로부터의 대규모 데이터세트에서 모델을 훈련할 수 있게 해주는 Spark 기반 추천 엔진도 있습니다. 이런 모델의 결과를 사용하여 다양한 형식(예: 사용자 추천)으로 맞춤설정된 권장 사항을 제공합니다.
ML 파이프라인의 다른 부분을 가동하기 위해 검색 및 관련성 기능에는 Elasticsearch를, 우리의 모든 ML 모델에 대한 입력 데이터 역할을 하는 데이터의 로그 기반 분산 스트리밍에는 Apache Kafka를, 모든 마이크로 서비스의 배포에는 Kubernetes를 사용합니다. Python, C++, Go, Scala 등의 언어를 사용하고, 온디바이스 통합을 위해서는 Java/Kotlin 및 Swift/Obj-C를 사용합니다.


온디바이스 ML: 'For This Photo'의 작동 방식

1단계: 이미지 분류

'For This Photo' 기능을 제공하는 모델을 빌드하기 위해, 먼저 이미지에 카테고리를 지정한 다음 그 카테고리에 적합한 효과를 주도록 고안된 프리셋을 추천할 수 있기를 원했습니다. 이미지 분류 프로세스를 묘사한 아래 도식을 참조하세요.

이미지 분류

사내 큐레이터 역할을 담당한 직원이 태그를 지정한 이미지 데이터를 출발점으로 삼았습니다. 이들 큐레이터는 사용자 동작을 면밀히 분석하는 사진 전문가로, 어떤 유형의 콘텐츠가 공유되고 어떤 트렌드가 전개되고 있는지 그 누구보다도 잘 알고 있습니다. 그들은 엔지니어링 팀이 모델에 대해 예술 작품, 인물, 생동감, 해안, 자연, 건축물, 빛과 그림자, 단색 등을 비롯한 이미지 카테고리를 떠올리고 제시하도록 도와주었습니다. 흔히 말하듯이, 머신러닝의 90%는 데이터 정리 작업입니다. 이런 단계는 모델이 기반으로 삼는 데이터가 신뢰할 수 있는 데이터인지 확인하는 데 도움이 되었습니다.
큐레이터와 협력하여 만든 분류된 데이터세트를 사용하여 SqueezeNet 아키텍처를 기반으로 TensorFlow에서 CNN 모델을 훈련했습니다. 이 아키텍처를 선택한 것은 크기는 더 작으면서도 정확도에는 큰 손실이 없기 때문이었습니다. Android에서 사용하기 위해 TFLiteConverter를 사용하여 훈련된 모델을 TensorFlow의 Saved Model 형식에서 TensorFlow Lite(.tflite) 형식으로 변환했습니다. 이 단계에서 초기에 발생한 버그의 원인 중 일부는 ML Kit가 Maven을 통해 참조한 TensorFlow Lite 라이브러리의 버전과 비교해 우리가 사용한 TFLiteConverter의 버전에 불일치가 발생했기 때문이었습니다. ML Kit 팀은 우리가 이런 문제를 해결하는 과정에서 무척 큰 도움을 주었습니다.


graph_def_file = “model_name.pb”
input_arrays = [“input_tensor_name”] # this array can have more than one input name if the model requires multiple inputs
output_arrays = [“output_tensor_name”] # this array can have more than one input name if the model has multiple outputs


converter = lite.TFLiteConverter.from_frozen_graph(
  graph_def_file, input_arrays, output_arrays)
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
코드 깃헙에서 확인하기

이미지에 카테고리를 할당할 수 있는 모델을 완성한 후 이를 앱에 추가하고 ML Kit를 사용해 이미지에 대한 추론을 실행할 수 있었습니다. 자체적으로 훈련한 맞춤 모델을 사용하고 있었으므로, ML Kit의 Custom Model API를 사용했습니다. 더 나은 정확도를 위해, 모델 변환에서 양자화 단계를 무시하고 ML Kit에서 부동소수점 모델을 사용하기로 했습니다. ML Kit에서는 기본적으로 양자화된 모델을 사용하는 것으로 가정하므로 이 점에서 약간 어려움이 있었습니다. 하지만 모델 초기화 과정에서 그다지 힘들이지 않고 몇몇 단계를 변경하여 부동소수점 모델을 지원할 수 있었습니다.


// create a model interpreter for local model (bundled with app)
FirebaseModelOptions modelOptions = new FirebaseModelOptions.Builder()
    .setLocalModelName(“model_name”)
    .build();
modelInterpreter = FirebaseModelInterpreter.getInstance(modelOptions);

// specify input output details for the model
// SqueezeNet architecture uses 227 x 227 image as input
modelInputOutputOptions = new FirebaseModelInputOutputOptions.Builder()
    .setInputFormat(0, FirebaseModelDataType.FLOAT32, new int[]{1, 227, 227, 3})
    .setOutputFormat(0, FirebaseModelDataType.FLOAT32, new int[]{1, numLabels})
    .build();

// create input data
FirebaseModelInputs input = new FirebaseModelInputs.Builder().add(imgDataArray).build(); // imgDataArray is a float[][][][] array of (1, 227, 227, 3)

// run inference
modelInterpreter.run(input, modelInputOutputOptions);
코드 깃헙에서 확인하기


2단계: 프리셋 추천
다음으로 해결할 문제는 이런 이미지 카테고리를 기반으로 하는 프리셋 추천이었습니다. 이런 프리셋을 만든 사내 Imaging 팀과 협업하여 이런 각각의 카테고리에 속한 이미지에 잘 맞는 프리셋의 목록을 도출했습니다. 이 프로세스에는 각각의 카테고리에 속한 많은 이미지에 대한 엄격한 테스트가 포함되었으며, 여기서 각각의 프리셋이 다양한 색상에 어떤 영향을 미치는지 분석했습니다. 마지막으로, 각각의 카테고리에 매핑되는 프리셋으로 큐레이팅된 카탈로그를 완성했습니다.

이미지에 적합한 프리셋 추천


이렇게 큐레이팅된 카탈로그는 멤버십 제공 서비스에 새로운 프리셋을 추가할 때마다 계속 변화합니다. 사용자가 앱을 업데이트하지 않아도 우리가 이런 목록을 서비스 중단 없이 더 쉽게 업데이트할 수 있도록 하기 위해, 서버에 이런 카탈로그를 저장하고 모바일 클라이언트가 최신 버전의 카탈로그를 확보하기 위해 주기적으로 체크인할 수 있도록 Go로 작성된 마이크로 서비스인 관련 API를 사용하여 카탈로그를 제공하기로 했습니다. 모바일 클라이언트는 이 카탈로그를 캐시하고 새 카탈로그 버전이 제공될 때만 카탈로그를 가져옵니다. 하지만 이 접근 방식에서는 이 기능을 처음으로 사용해 보기 전에 인터넷에 연결하지 않아 앱이 API와 통신하고 이런 카탈로그를 다운로드할 기회가 없었던 사용자의 경우 '콜드 스타트' 문제가 발생합니다. 이 문제를 해결하기 위해 앱에 기본 버전의 카탈로그를 함께 제공하기로 했습니다. 이에 따라, 모든 사용자가 인터넷 연결 여부와는 무관하게 이 기능을 사용할 수 있으며, 이는 이 기능을 만들면서 처음부터 생각했던 목표 중 하나이기도 합니다.

결과 및 결론

'For This Photo' 기능을 통해 프리셋을 이용한 편집 기능을 더 쉽게 탐색할 수 있도록 한다는 목표를 달성했습니다. 새로운 프리셋을 제공하더라도 정작 회원들이 가치 있게 이용하지 못한다면 창작 활동의 진행에 오히려 방해가 된다고 생각합니다. 그래서 이런 상황을 개선하고 싶었던 것입니다. 더 많은 사용자가 단지 새로운 프리셋을 발견할 뿐 아니라 창작품에 가장 어울리는 프리셋에 온전히 관심을 집중하도록 돕고 싶었습니다.
'For This Photo' 기능을 계속 향상하여 다른 이미지 특성을 물론이고 사용자의 커뮤니티 활동(예: 팔로우, 즐겨찾기, 다시 게시)도 기반으로 하여 어울리는 프리셋을 추천해 드리고 싶습니다. 그 밖에도, 그런 권장 사항에 대해 더 폭넓고 다양한 컨텍스트를 제공하고 창작자 커뮤니티가 서로를 발견하고 서로에게 영감을 주는 환경을 조성하고도 싶습니다.

또한 이 기능의 미래를 전망하면서 과거도 되돌아보고 있습니다. TensorFlow Lite와 ML Kit가 없었더라면 이 기능과 VSCO의 온디바이스 ML 기능을 만들 수 없었으리라는 점을 잘 알고 있습니다. 설레는 기대를 안고서 이 분야에 계속 투자하고 앞으로도 이 기술을 활용하는 더욱 다양한 기능을 만들도록 하겠습니다.

Flutter를 이토록 즐겁고 생산적으로 만들어주는 요인 중 큰 부분은 바로 Flutter 커뮤니티입니다. 우리는 이처럼 바람직한 커뮤니티가 계속 건강한 모습으로 유지되길 진정으로 바라기에, 현재 Flutter 커뮤니티의 문화에 대한 관찰한 사항과 이를 잘 보존할 방법에 관한 몇 가지 아이디어를 공유해야겠다고 생각했습니다.

개방성

우리가 여기서 생각하는 개방성 이라는 단어에는 세 가지 의미가 있습니다. 열린 마음 이라는 의미의 개방성, 액세스 가능하다는 의미의 개방성, 오픈소스 관점에서의 개방성 이 바로 그것입니다. 오픈소스라는 관점은 훌륭하며 마치 열대우림에서 온갖 생물종이 풍성하고 다양하게 서식하듯 우리의 패키지 시스템을 풍성하게 만들어주긴 하지만, 우리는 문화라는 측면에서 주로 열린 마음과 액세스 가능성의 두 측면에 대해 생각합니다. Flutter 커뮤니티에서 뉴비를 따뜻하게 맞이해주고 인내심을 가지고 완벽성을 기하며 다양한 질문에 답해주면서 뉴비가 요령을 터득하도록 도와주는 훈훈한 모습을 보는 건 정말 멋진 일이었습니다. 이런 문화는 우리 커뮤니티가 성장하는 데 도움이 되고 배경과는 무관하게 누구라도 커뮤니티에 초대하고 싶은 마음이 들게 합니다. Flutter 개발자가 코드 스니펫을 공유하고 기꺼이 다른 개발자의 문제를 디버깅하는 데 힘을 써주는 건 정말 보기 좋은 모습입니다.


물론, 열린 마음은 단지 뉴비에게만 그런 마음으로 대한다는 것보다 훨씬 더 큰 의미를 가집니다. 어떤 아이디어를 구현하는 방법으로 한 가지 방법만이 아니라 여러 방법이 제시될 때가 많은데, 우리는 Flutter 커뮤니티에서 문제가 제기될 때 품위 있게 문제를 해결하거나 경우에 따라 적절히 알고리즘을 구현하는 다양한 방법을 허용한다는 점을 무척 높이 사는 바입니다. 그 점 역시 강력한 장점입니다.


일정 수준의 표준화는 좋습니다. 소규모 팀이 아마도 표준화된 코드 스타일을 가지고 있어야 할 것입니다. 하지만 기술 커뮤니티에서는 일을 처리하는 '올바른' 방법에 대해 좀 지나치게 극성스러워지기 쉬운 게 사실입니다. 다양한 팀이 다양한 코드 스타일을 가지는 건 괜찮습니다. 다양한 앱에서 다양한 상태 관리 접근 방식을 사용하는 것 역시 괜찮습니다. 어떤 사람은 스페이스 대신 탭을 사용하는데, (*15분 동안 눈에 띌 정도로 헤매긴 했지만*) 그것 역시 괜찮습니다.


다시 말씀드리지만, 기술 커뮤니티에서는 어떤 아이디어에 대해 점점 경직된 자세를 취하고 조직에서 확립되어 절대 바뀌지 않는 지식이 늘어나고 이를 광신도적인 믿음으로 절대 바꾸지 못하도록 방어하게 되는 상황이 얼마나 자연스러운 일인지 간과하기 쉽습니다.
하지만 Flutter 커뮤니티에서는 그와 같은 현상을 전혀 본 적이 없습니다. 열린 마음, 관대함, 호기심, 철두철미한 자세를 가지고서 선입견이 없는 초심자의 마음가짐으로 문제에 접근하는 것이 모든 이에게 도움이 됩니다. 이는 뉴비에게 동기부여가 될 뿐 아니라, 어떤 주제 영역에 대해서만 이해도가 깊은 Flutter 베테랑의 이해 범위를 더욱 넓히고 증진시켜 줍니다. 또한 이런 접근 방식을 취하면 다른 사람들이 내 아이디어에 더 적극적으로 반응하게 됩니다. 우리는 Stack Overflow와 다양한 Slack 채널에서 종종 이처럼 긍정적인 반응을 보며 큰 보람을 느낍니다. 슈퍼파워로서의 취약점도 함께 살펴보세요.


겸손한 자세

우리는 Flutter 개발자가 뉴비를 깔보지 않고 성심껏 도와주는 모습을 봅니다. 이건 정말 강력한 효과를 발휘합니다. 다른 사람들 앞에서 완벽해 보이고 싶어 하는 건 무척 자연스러운 일입니다. 하지만 너무 지나치면 진실된 모습을 숨기고 비현실적인 기대를 하는 문화로 이어집니다. 이와 같은 부분을 제대로 짚지 않고 방치하면 피드백 루프를 통해 통제 범위를 벗어날 수 있습니다. 사소하지만 남을 헐뜯는 듯한 표현이 그 발단이 될 수 있으며, 이를 방치하다가는 결국 가면증후군, 불안정함, 초조감, 냉혹함과 같은 잘못된 분위기가 만연하게 됩니다.
우리가 실수를 저지르는 다양한 Flutter Boring Show 에피소드를 통해 이런 점을 간파하셨기를 바랍니다. (우리 모두는 분명히 실수를 저지르기 때문입니다!) Boring Show의 콘셉트는 보기 좋게 편집하지 않고 개발자가 저지르는 온갖 실수, 막다른 길에 봉착하는 상황, 궁리하느라 시간이 걸리는 상황 등을 모두 포함해 개발 과정을 고스란히 그대로 보여주자는 것입니다.
이는 우리 팀이 예비 Flutter GDE(Google Developer Expert)에게서 찾는 주요 자질 중 하나입니다. 우리는 지구상에서 Flutter에 대해 가장 정통한 지식을 가진 Flutter GDE가 겸손함과 공감 능력을 보여주기를 기대합니다. 우리는 똑똑하지만 거만한 사람들이 Flutter 커뮤니티를 대표하기를 바라지 않습니다. 고맙게도, 우리는 예비 GDE 중 그 누구와도 문제와 갈등을 겪은 적이 없습니다. 겸손함의 미덕에 그에 담긴 메시지는 항상 공감을 불러일으켰으며, 어떤 의미인지 설명할 필요조차 없었습니다. 그게 바로 우리 커뮤니티의 성격을 잘 보여줍니다.
다행히도, 우리는 이런 점을 Flutter 엔지니어링 팀에서도 보게 됩니다. 한 가지 예만 들자면, 다른 업적도 많지만 Flutter의 창시자 중 한 명이자 예전에는 HTML 규격의 편집자로도 활동했던 Ian은 우리가 아는 여느 엔지니어처럼 그저 수수하고 겸손한 자세를 보여주는 엔지니어입니다. 아래 동영상에서 Ian을 만나볼 수 있는데, Slivers의 작동에 대해 분명히 모르는 부분도 있음을 솔직히 드러내 보입니다. 그런 진솔한 모습에서 그에 대해 실망하거나 존경심이 약해지는 게 아니라, 오히려 더욱 그를 존경하게 됩니다.



다른 기술의 존중

어떤 한 가지 기술에만 몰입하다 보면 다른 모든 기술이나 아이디어는 마치 하찮은 것인 양 여기기 십상입니다. 특히 경쟁 기술에 대해 그런 자세를 취하기 쉽습니다.
Flutter는 과대 광고나 광신적 열광이 필요하지 않습니다. Dart를 이용해 반응형 UI를 신속히 작성하거나, iOS와 Android(그리고 웹과 데스크톱)에서 작동하는 신속한 반응형 앱을 개발하거나, 풍부한 패키지 생태계를 쉽게 활용할 수 있도록 하는 등, Flutter를 사용할 타당한 이유가 많기 때문입니다.
하지만 Flutter에는 적합하지 않은 사용 사례를 다루는 사람들도 있으므로, Flutter 예찬자로서 할 수 있는 최선의 방안 중 하나는 이런 사람들에게는 Flutter를 사용하지 말라고 설득하는 것입니다. 그러면 그들이 큰 불편을 겪지 않고 실망하지 않도록 할 수 있습니다. 그들은 기술적인 식견이 매우 넓어 공명정대한 시각으로 상황을 판단해서 올바른 방향으로 권장한다는 점을 깨닫고는 더욱 존중심을 보여줄 것입니다.
유감스럽게도, Filip은 이미 Flutter 커뮤니티에서 다른 기술 커뮤니티를 대놓고 조롱하는 모습을 본 적이 있습니다. 하지만 그런 흐름이 만연한 것은 아니었으며, 커뮤니티에 속한 다른 이들이 그런 잘못된 모습에 이의를 제기했습니다. 그런 모습에서 우리는 커뮤니티의 건강한 성장을 낙관하게 됩니다.

이런 문화를 유지하는 이유는 뭘까요?

이런 커뮤니티 문화는 Flutter가 지닌 막대한 강점이기 때문입니다. 현실적으로 생각해봅시다. 어떤 기술에 대한 개발자의 경험은 기술 그 자체와 그 기술을 중심으로 하는 커뮤니티의 합입니다. 어떤 커뮤니티가 순전히 광적인 지지자, 우매한 추종자, 극단적인 과대 포장을 일삼는 사기꾼 같은 사람으로만 구성되어 있다면, 기술적 우수성이 어떻든 많은 이의 자발적인 참여를 이끌어내지 못할 것입니다.
건강한 커뮤니티는 탄탄하게 성장하는 커뮤니티입니다. 성장하는 동시에 점점 편협해지는 커뮤니티가 있다면 그 이면에는 숨겨진 비용이 있을 수 있습니다. 그렇습니다. 어느 정도 새로운 사람들이 참여하고 있지만, 만약 커뮤니티에서 뉴비를 더욱 진심으로 따뜻하게 맞이해주는 분위기라면 얼마나 많은 사람이 추가로 참여할지 상상하실 수 있으세요? 이 커뮤니티로 우리 의 발길을 이끈 이처럼 훌륭한 장점을 잘 보존하고 앞으로도 계속 새롭게 참여할 분들에게 문호를 활짝 열어놓고 싶습니다.

커뮤니티 문화를 어떻게 보존할 수 있을까요?

하향식 방법으로는 이런 소중한 가치를 보존할 길이 없습니다. Flutter는 공개 커뮤니티입니다. 우리 커뮤니티 고유의 가치는 구성원들이 매일 내리는 의사결정을 통해 보존될 것입니다. 우리 모두가 각자 새로운 구성원을 어떻게 환영할지, 지식을 어떻게 공유할지, 최근의 실수에 대해 얼마나 진실될지, 경쟁 프레임워크에 대해 얼마나 잘 말할지 등의 사소한 결정 말입니다. 그 모든 것이 합쳐져 Flutter 커뮤니티가 됩니다.
우리는 개방성, 겸손함, 생태계 내 타인에 대한 존중에 초점을 맞춤으로써 모든 이를 위해 훌륭한 개발자 환경을 계속 육성해 나갈 것입니다. 이 글을 통해 우리가 커뮤니티에서 목격하는 일들을 식별하고 거론함으로써 커뮤니티 문화의 보존에 일조하기를 바랍니다. 결국, 우리가 그런 훌륭한 문화를 보유하고 있음을 확실히 깨닫지 않고는 제대로 보존하기 어렵습니다.

오해를 피하기 위한 당부 사항

  • 모범 사례와 훌륭한 코드를 보여주면 안 된다는 뜻이 아닙니다. 얼마든지 훌륭한 성과를 드러내실 수 있지만, 다른 사람을 깔보지는 말자는 뜻입니다.
  • 어떤 표준을 마련하고 '일을 처리하는 주된 방법'을 정해 두어서는 안 된다는 뜻이 아닙니다. 얼마든지 표준을 마련할 수 있지만, 다른 대안을 무시하지는 말자는 뜻입니다.
  • 열광적인 태도를 취하면 안 된다는 뜻이 아닙니다. Flutter를 열렬히 옹호하고 열광적 지지 의사를 밝힐 수 있지만, 경쟁 기술에 대한 혐오감이나 적대감을 보이지는 말자는 뜻입니다.
  • Flutter에 대해 들뜨고 기대감을 품어서는 안 된다는 뜻이 아닙니다. 얼마든지 신나는 마음을 표현할 수 있지만, Flutter가 특정 작업에는 적합한 도구가 아닐 수도 있는 상황과 이유는 정확히 알아야 한다는 뜻입니다.


Flutter가 계속 사용자 수를 늘려나가며 성장함에 따라, 우리 커뮤니티의 역사도 중요한 시점을 맞이하고 있으며 우리 모두가 각자 이처럼 중요한 역사의 순간에 중요한 역할을 맡고 있습니다! 모든 개발자에게 훌륭한 개발 환경을 계속 육성하기 위해 올바른 방향으로 성장해 나갑시다. 인터넷에서 다른 사람들과 대화할 때는 개방성, 겸손함, 다른 생태계에 대한 존중을 원칙으로 삼으시기 바랍니다. Flutter는 행동 강령을 마련해 두고 있으며, Flutter 커뮤니티에서 언짢은 느낌을 받거나 부당한 일을 당하신 경우 우리를 비롯해 팀의 누군가에게 알려주십시오. Flutter 커뮤니티에 처음 오신 분이라면 진심으로 환영합니다! 함께하게 되어 무척 반갑고 기쁩니다.