• 텐서플로우로 이미지인식 모바일앱 6-1 (iOS 편: 예제 실행) + 자체 학습한 그래프로 바꿔 쓸 때 주의사항 [머신러닝/딥러닝]
  • 공돌이
    조회 수: 6223, 2017.07.05 18:31:44
  • 시간이 꽤나 흘렀네요...^^;; 

     

    원래는 간단하게 iOS 용 예제앱을 빌드해서 실행해보는것 까지만 해보려 했으나...

    기다리는 분이 계신데 너무 성의 없는것 같기도 하고, (사실 간단하거든요...-_-;;)

    최근 깃헙 마스터 버전에는 cocoaPods 를 이용해서 좀 더 간단히 빌드하는 방법도 새로 나오고 해서 간단하게나마 아래와 같이 세 파트로 나누어 진행하려고 합니다.

     

    6-1: iOS 예제 실행해 보기 (simple 예제, Camera 예제) + 자체적으로 학습한 그래프를 올릴때 주의사항

    6-2: simple 예제 코드 및 프로젝트 간단설명 (주로 텐서플로우 적용부분만) - 자체적으로 앱을 만들려고 할때 필요한 부분 위주로 설명

    6-3: memory mapped 그래프를 읽을 수 있도록 simple 예제코드 살짝 수정해보기 (simple예제는 원래 메모리매핑된 그래프를 읽도록 되어있지 않음)

     

    이번 시간은 iOS용으로 만들어져 있는 예제를 소스는 (거의) 따로 건드리지 않고 빌드해서 실행해 보도록 하겠습니다.

    simple 예제는 사진파일을 읽어서 바로 inference 결과를 뿌려주는 앱이고요,

    camera 예제는 실시간으로 카메라에 찍히는 물체를 인식해줍니다.

     

    simple 예제에서는 quantized 그래프를, 그리고 camera 예제는 한줄만 수정해서 memory mapped 된 그래프를 읽어서 작업을 수행하겠습니다.

    (단, camera 예제는 시뮬레이터에서 안돌아가기 때문에 실제 기기에서 인식하는것까지는 테스트를 못해봤습니다 . 관심있으신분은 한번 돌려보시고 결과를 댓글에 남겨주시면 다른분들도 도움될것 같아요)

     

    (참고)

    일단 제가 돌려본 바로는 iOS용이 속도가 훨씬 빠릅니다. 아이폰7과 LG G5 두 기기에서 돌려봤는데, inference (인식) 수행속도가 아이폰이 훨씬 빠르더군요.

    (quantized 된 그래프 기준)

     

    memory mapped 된 그래프로 하면 모바일에선 속도가 훨씬 빨라진다고 하는데, iOS는 바로 적용할 수 있으나 현재 안드로이드는 memory mapped 그래프를 읽을 수 없습니다. (JNI쪽을 수정해줘야 함...아직 손을 못대고 있습니다) 

     

     

     

    I. 그래프 파일 다운로드 받기

    이번 튜토리얼에서는 지난번에 직접 훈련시킨 그래프를 사용하지 않고 (꽃이름만 훈련시켰으니), 구글에서 잘 훈련시켜놓은 그래프를 다운로드 받아서 쓰겠습니다.

    원하시면 본인이 직접 훈련한 그래프를 사용하셔도 상관없습니다...^^;; (소스 일부 수정 필요)

     

    우선 터미널을 열고 그래프를 다운로드 할 폴더를 만들고 다운로드 합니다.

    저는 디렉토리관리를 위해 tensorflow 가상환경에 사용하던 tensor 폴더 안에 그래프폴더를 만듭니다.

    다운받은 파일은 zip 으로 압축되어 있으므로 압축도 풀어줍니다.

     

    cd tensor

    mkdir graph

     

    curl -o graph/inception5h.zip https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip \

    && unzip graph/inception5h.zip -d graph/inception5h

     

    graph/inception5h 폴더 아래를 보시면

     

    - tensorflow_inception_graph.pb

    - imagenet_comp_graph_label_strings.txt

     

    두개 파일이 보입니다.

    pb 파일은 모바일에서 사용할 수 있도록 (아마도...^^) optimized 된 그래프이므로,  다음의 명령어로 quantize 하거나, memory mapped graph 로 변환하실 수 있습니다.

    (input 과 output name 이 다른점에 주의!)

    (여러번 bazel build 하다보면 기존의 툴들이 삭제될 수 있는데, 그럼 이전 튜토리얼에서 했던 bazel build 들을 다시 해야 합니다)

     

    이번 파트는 우선 이 데모그래프를 그대로 사용할 것이므로, 해당 example 폴더 안에 있는 data 폴더 안에 복사해줍니다.

    파인더상에서 직접 복사해 주셔도 되고 터미널에서는 (simple example 의 경우)

     

    cp graph/inception5h/* tensorflow/tensorflow/examples/ios/simple/data/

     

    하셔서 예제 폴더 안의 data 폴더로 복사해 넣어주세요.

     

     

    II. 빌드하고 실행해보기

     

    빌드 방법은 현재 두가지가 있습니다.

    1) CocoaPods 이용: (안드로이드 JCenter와 비슷한 오픈소스 리퍼지터리인듯) 최근 github 소스 부터 지원됩니다. 소스를 빌드안해도 되니 상대적으로 빨리 빌드가능

    2) 소스 빌드 : 소스빌드 작업자체는 간단한데 시간이 좀 오래 걸립니다 (30분 이상 - 사양에 따라 다름...전 한시간정도 걸렸던듯)

     

    II-1 CocoaPods 이용 (최근 깃헙 마스터버전을 클론한 경우 적용가능)

    : 이 방법은 저도 처음 보는거라, 방법만 간단히 알려드리겠습니다 (이번 튜토리얼의 핵심은 소스빌드.)

    최근 깃헙 마스터버전에서는 iOS example 폴더 위치가

    tensorflow/tensorflow/contrib/ios_examples/ 에서

    tensorflow/tensorflow/examples/ios/ 로 변경되었습니다.

     

    그 안에 보시면  benchmark, simple, camera 세개의 폴더가 보입니다.

     

    camera 예제는 좀 있다가 보기로 하고 우선 simple 예제를 빌드하겠습니다.

     

    현재 cocoapods 를 사용하지 않고 계시다면 우선 cocoapods를 다음의 명령어로 설치해줍니다.

     

    sudo gem install cocoapods

     

    터미널에서 simple 예제 폴더로 이동한 후 pod 를 설치해줍니다.

    (네트워크사정에 따라 다르지만 시간이 쫌 걸립니다 : 약 450MB - 그래도 라이브러리 소스에서 빌드하는것 보다는 훠얼씬 빨라요!)

     

    cd tensorflow/tensorflow/examples/ios/simple

    pod install

     

    어... 끝입니다 ㅋ

     

    단, simple 안에 있는 tf_simple_example.xcodeproj 가 아니라 tf_simple_example.xcworkspace 를 실행하셔야 합니다. (pods는 이렇게 해야 하나보네요)

     

    파인더에서  tf_simple_example.xcworkspace 를 더블클릭하시거나 터미널에서 (simple 폴더 안에 있으니까)

     

    open  tf_simple_example.xcworkspace

     

    하신 후, 실행하시면 됩니다.

     

    benchmark 예제나 카메라 예제의 경우도 진행과정은 simple example과 동일하구요, 단지 실행 시에 xcodeproj 파일이 아니고 xcworkspace 를 실행하시기만 하면 됩니다.. 

     

    주의:

    Camera example의 경우에는 시뮬레이터에서는 실행이 안되고 실제 기기에 넣어서 실행하셔야 합니다. (아마 하드웨어컨트롤때문에 그런듯)

     

    혹시 돌리는 데 잘 안되는 부분이 있으시다면, 질문게시판에 올려주세요...저도 잘 아는건 아니지만, 그래도 최대한 돕겠습니다!

     

     

    II-2 소스로부터 라이브러리 직접 빌드해서 실행해보기

     

    라이브러리를 빌드하기 위해서 automake 와 libtool이 필요합니다.

    설치되어 있지 않다면, 설치해 주세요.

     

    brew install automake

    brew install libtool

     

     

    실제 라이브러리 빌드는 쉘 스크립트 하나만 실행해 주면 되므로 간단한데, 시간이 좀 걸립니다.

     

    터미널에서 텐서플로우 깃헙 소스안에 빌드 스크립트(build_all_ios.sh) 를 실행해 주세요.

     

    cd ~/tensor

    tensorflow/contrib/makefile/build_all_ios.sh
    

     

    빌드가 진행되는 동안 그동안 미뤄뒀던 다른 일을 하시거나 간식을 먹고 옵니다. 간식을 먹고 와도 돌고 있다면 커피도 한잔 하고 오세요...

     

    빌드가 완료되면, 이제 프로젝트를 실행합니다. 

     

    파인더에서 tensorflow/tensorflow/examples/ios/ 밑의 simple / camera / benchmark 폴더 안에 있는 xcodeproj 파일을 더블클릭하시거나,

     

    터미널에서 해당 서브폴더로 이동 후 (simple example의 경우)

     

    open tf_simple_example.xcodeproj

     

    해주시면 엑스코드가 실행되면서 프로젝트가 열리겠지요.

    실행해주심 됩니다. 

    마찬가지로 camera example 은 실기기에서 실행하셔야 합니다.

     

    참고:

    build_all_ios.sh 스크립트는 몇개의 스크립트를 엮어서 한방에 라이브러리를 만들어주는 스크립트입니다. 

    다음번에 기회가 되면 각각 어떤 스크립트가 실행되는지, 최적화하는 방안 등에 대해서 따로 글을 올리겠습니다.

     

     

    II-3. Camera example 에서 memmapped_graph 를 사용하려면?

    기존 데모에서 simple example은 memmapped graph 를 읽을수 있도록 되어있지 않기 때문에, 읽도록 하려면 코드 수정이 좀 필요합니다. (이거는 별도로 다룰 예정)

    반면 camera example 은, 소스코드만 살짝 수정해주면 바로 읽을 수 있도록 되어있습니다.

     

    이 파트에서는 camera example 의 소스코드를 살짝 수정해서 memmapped graph 를 읽을 수 있도록 해보겠습니다.

    우리가 지난시간에 직접 학습시킨거 말고 이 글의 앞부분에서 다운로드 받은 인셉션그래프를 memmapped graph 로 변환해봅시다.

     

    우선 다운받은 인셉션 그래프를 quantize 한다음,

    수정: ~/tensor 폴더에서 아래의 명령어를 수행하실 때,  --input 과 --output 파라미터에 pb파일이 있는 경로도 같이 넣어주세요

            보통 실행파일이 있는 경로와 pb 파일이 있는 경로가 맞지 않을 때 not found 등의 오류가 발생합니다.

            즉, 현재 디렉토리 기준으로 quantize_graph 가 있는 경로를 상대경로든 절대경로든 정확히 넣어주시고, pb파일이 현재디렉토리에 없다면 그 경로도 정확히 넣어주셔야 합니다.

     

    tensorflow/bazel-bin/tensorflow/tools/quantization/quantize_graph  \

    --input=graph/inception5h/tensorflow_inception_graph.pb  \

    --output=graph/inception5h/rounded_graph.pb \

    --output_node_names=softmax1 \

    --mode=weights_rounded

     

    memmapped그래프로 다시 변환해줍니다.

     

    tensorflow/bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format   \

    --in_graph=rounded_graph.pb   \

    --out_graph=mmapped_graph.pb 

     

    Xcode를 실행하신 후, Camera example 을 열고, (이번에는 xcodeproj)

     

    data 폴더에 tensorflow_inception_graph.pb 파일을 집어넣으셨다면 삭제해주세요.

    안집어넣으신 상태라면 해당 그래프이름이 빨갛게 떠 있을거예요. 그 경우도 삭제해주세요.

     

    그리고 만들어진 mmapped_graph.pb 파일을 파인더에서 마우스로 드래그 하여 xcode 상의 data 폴더로 드롭합니다.

     

    그러면 아래와 같은 화면이 뜨죠.  copy items if needed 와 add to target 에 체크가 안되어 있다면 체크하시고 Finish 버튼을 눌러줍니다.

     

    Monosnap 2017-07-05 18-00-02.png

     

    주의: Add to Target 에 체크해주셔야 리소스가 등록됩니다!

     

    그다음 소스 중에서 CameraExampleViewController.mm 파일을 에디터로 열어주세요. 그리고, 윗부분에 보시면 아래와 같은 각종 변수값 정의부분이 있습니다.

     

    // If you have your own model, modify this to the file name, and make sure
    // you've added the file to your app resources too.
    static NSString* model_file_name = @"tensorflow_inception_graph";
    static NSString* model_file_type = @"pb";
    // This controls whether we'll be loading a plain GraphDef proto, or a
    // file created by the convert_graphdef_memmapped_format utility that wraps a
    // GraphDef and parameter file that can be mapped into memory from file to
    // reduce overall memory usage.
    const bool model_uses_memory_mapping = false;
    // If you have your own model, point this to the labels file.
    static NSString* labels_file_name = @"imagenet_comp_graph_label_strings";
    static NSString* labels_file_type = @"txt";
    // These dimensions need to match those the model was trained with.
    const int wanted_input_width = 224;
    const int wanted_input_height = 224;
    const int wanted_input_channels = 3;
    const float input_mean = 117.0f;
    const float input_std = 1.0f;
    const std::string input_layer_name = "input";
    const std::string output_layer_name = "softmax1";
     

     

    이 중에서 붉은 색으로 표시된 부분을 아래와 같이 수정해 주세요. (memmapped graph를 쓰겠다는 불린값과 그래프파일명을 방금복사해넣은 이름으로 수정)

     

    // If you have your own model, modify this to the file name, and make sure
    // you've added the file to your app resources too.
    static NSString* model_file_name = @"mmapped_graph";
    static NSString* model_file_type = @"pb";
    // This controls whether we'll be loading a plain GraphDef proto, or a
    // file created by the convert_graphdef_memmapped_format utility that wraps a
    // GraphDef and parameter file that can be mapped into memory from file to
    // reduce overall memory usage.
    const bool model_uses_memory_mapping = true;
    // If you have your own model, point this to the labels file.
    static NSString* labels_file_name = @"imagenet_comp_graph_label_strings";
    static NSString* labels_file_type = @"txt";
    // These dimensions need to match those the model was trained with.
    const int wanted_input_width = 224;
    const int wanted_input_height = 224;
    const int wanted_input_channels = 3;
    const float input_mean = 117.0f;
    const float input_std = 1.0f;
    const std::string input_layer_name = "input";
    const std::string output_layer_name = "softmax1";
     

     

    네, 여기까지 하셨으면 이제 빌드하고 실행해보시기 바랍니다. 카메라에 잡히는 이미지를 인식해 줍니다. 끝.

    안되면 글 남겨주셈...^^;;

     

     

    II-4. 내가 만든 그래프파일을 넣고 싶다면? 

     

    간단한데, 소스파일을 살짝 수정해줘야 합니다.

     

    quantized 된 그래프를 사용한다고 가정하고, (rounded_graph.pb 와 retrained_labels.txt)

    II-3 에서 memmapped graph를 넣어주는 방식과 동일하게 엑스코드에서 data폴더의 기존 그래프파일과 라벨파일을 삭제하신 후 직접 만드신 파일을 드래그&드롭 해서 복사/등록해주세요.

     

     

    * simple example 의 경우

    data folder에 보시면 grace_hopper.jpg 가 있습니다. 테스트를 할 이미지인데, 우리가 학습시킨 꽃 그래프와는 좀 상관없으니까, 테스트에 사용할 적당한 이미지를, 그래프파일 복사해넣은것과 같은 형식으로 (드래그&드롭) 복사/등록해주세요. 아래 설명에선 dandelion.jpg 를 넣었다고 가정하겠습니다.

     

    RunModelViewController.mm 파일을 에디터로 연 후에,

     

    소스에서 아래 부분들을 각각  찾아서 다음 줄에 붉은색 값으로 변경해 주세요. ( camera example과는 다르게 global 변수로 선언되어있지 않습니다)

    FYI, 아래에서 저는 기존의 소스를 주석처리하고 아랫쪽에 제가 수정한 내용으로 추가했습니다.

     

    // NSString* network_path = FilePathForResourceName(@"tensorflow_inception_graph", @"pb");

    NSString* network_path = FilePathForResourceName(@"rounded_graph", @"pb");

     

    ...

     

    // NSString* labels_path = FilePathForResourceName(@"imagenet_comp_graph_label_strings", @"txt");

    NSString* labels_path = FilePathForResourceName(@"retrained_labels", @"txt");

     

    ...

     

    // NSString* image_path = FilePathForResourceName(@"grace_hopper", @"jpg");

    NSString* image_path = FilePathForResourceName(@"dandelion", @"jpg");  

     

    ...

     

    //  const int wanted_width = 224;
     // const int wanted_height = 224;
     // const int wanted_channels = 3;
     // const float input_mean = 117.0f;
     // const float input_std = 1.0f;

     

      const int wanted_width = 299;
      const int wanted_height = 299;
      const int wanted_channels = 4;
      const float input_mean = 128.0f;
      const float input_std = 128.0f;
    ...

     

    //  std::string input_layer = "input";
    //  std::string output_layer = "output";

      std::string input_layer = "Mul";
      std::string output_layer = "final_results";
     

     

    수정하셨으면, 빌드해서 실행~~!

     

    * 카메라 example의 경우

     

    simple example과 동일하게 graph/label 파일을 복사/등록하신 후에, 

    위의 memmapped graph 적용할때에 수정하는것과 같은 부분을 아래와 같이 수정해주세요.(붉은색으로 표시)

    // If you have your own model, modify this to the file name, and make sure
    // you've added the file to your app resources too.
    static NSString* model_file_name = @"rounded_graph";
    static NSString* model_file_type = @"pb";
    // This controls whether we'll be loading a plain GraphDef proto, or a
    // file created by the convert_graphdef_memmapped_format utility that wraps a
    // GraphDef and parameter file that can be mapped into memory from file to
    // reduce overall memory usage.
    const bool model_uses_memory_mapping = false;
    // If you have your own model, point this to the labels file.
    static NSString* labels_file_name = @"retrained_labels";
    static NSString* labels_file_type = @"txt";
    // These dimensions need to match those the model was trained with.
    const int wanted_input_width = 299;
    const int wanted_input_height = 299;
    const int wanted_input_channels = 4;
    const float input_mean = 128.0f;
    const float input_std = 128.0f;
    const std::string input_layer_name = "Mul";
    const std::string output_layer_name = "final_result";

     

     

    그리고 빌드&실행!

     

     

    ==========================================

     

    후우와...처음 생각했던거보다 너무 늦어져서 기다리셨던 분들께는 죄송스럽기 짝이 없네요...ㅠㅜ 

    원래 iOS쪽은 자신이 없어서 한편으로 퉁치려고 하다가... 너무 성의없는것 같아 중간에 갈아엎고 다시 쓰다가, 또 다른일땜에 손 놨다가...여튼...

     

    iOS쪽은 아직 두 편이 더 남아 있네요. 조만간 다음편 또 올리겠습니다!

     

    도움 되셨으면 여기 댓글 하나씩 남겨주시면 기운날것 같아요...ㅎㅎ

     

    그리고, 그럴 일은 없겠지만 혹시라도 퍼가시는 경우에는 *꼭* 출처를 밝혀주시고 링크를 걸어주세욥!