學習筆記TF066:TensorFlow移動端應用,iOS、Android系統實踐

来源:http://www.cnblogs.com/libinggen/archive/2017/11/14/7830932.html
-Advertisement-
Play Games

TensorFlow對Android、iOS、樹莓派都提供移動端支持。 移動端應用原理。移動端、嵌入式設備應用深度學習方式,一模型運行在雲端伺服器,向伺服器發送請求,接收伺服器響應;二在本地運行模型,PC訓練模型,放到移動端預測。向服務端請求數據可行性差,移動端資源稀缺。本地運行實時性更好。加速計算 ...


TensorFlow對Android、iOS、樹莓派都提供移動端支持。

移動端應用原理。移動端、嵌入式設備應用深度學習方式,一模型運行在雲端伺服器,向伺服器發送請求,接收伺服器響應;二在本地運行模型,PC訓練模型,放到移動端預測。向服務端請求數據可行性差,移動端資源稀缺。本地運行實時性更好。加速計算,記憶體空間和速度優化。精簡模型,節省記憶體空間,加快計算速度。加快框架執行速度,優化模型複雜度和每步計算速度。
精簡模型,用更低權得精度,量化(quantization)、權重剪枝(weight pruning,剪小權重連接,把所有權值連接低於閾值的從網路移除)。加速框架執行,優化矩陣通用乘法(GEMM)運算,影響捲積層(先數據im2col運行,再GEMM運算)和全連接層。im2col,索引圖像塊重排列為矩陣列。先將大矩陣重疊劃分多個子矩陣,每個子矩陣序列化成向量,得到另一個矩陣。

量化(quantitative)。《How to Quantize Neural Networks with TensorFlow》https://www.tensorflow.org/performance/quantization 。離散化。用比32位浮點數更少空間存儲、運行模型,TensorFlow量化實現屏蔽存儲、運行細節。神經網路預測,浮點影響速度,量化加快速度,保持較高精度。減小模型文件大小。存儲模型用8位整數,載入模型運算轉換回32位浮點數。降低預測過程計算資源。神經網路雜訊健壯笥強,量化精度損失不會危害整體準確度。訓練,反向傳播需要計算梯度,不能用低精度格式直接訓練。PC訓練浮點數模型,轉8位,移動端用8位模型預測。
量化示例。GoogleNet模型轉8位模型例子。下載訓練好GoogleNet模型,http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz 。

bazel build tensorflow/tools/quantization:quantization_graph
bazel-bin/tensorflow/tools/quantization/quantization_graph \
--input=/tmp/classify_image_graph_def.pb \
--output_node_names="softmax" --output=/tmp/quantized_graph.pb \
--mode=eightbit

生成量化後模型大小隻有原來的1/4。執行:

bazel build tensorflow/examples/label_image:label_image
bazel-bin/tensorflow/examples/label_image/label_image \
--image=/tmp/cropped_panda.jpg \
--graph=/tmp/quantized_graph.pb \
--labels=/tmp/imagenet_synset_to_human_label_map.txt \
--input_width=299 \
--input_height=299 \
--input_mean=128 \
--input_std=128 \
--input_layer="Mul:0" \
--output_layer="softmax:0"

量化過程實現。預測操作轉換成等價8位版本操作實現。原始Relu操作,輸入、輸出浮點數。量化Relu操作,根據輸入浮點數計算最大值、最小值,進入量化(Quantize)操作輸入數據轉換8位。保證輸出層輸入數據準確性,需要反量化(Dequantize)操作,權重轉回32位精度,保證預測準確性。整個模型前向傳播用8位整數支行,最後一層加反量化層,8位轉回32位輸出層輸入。每個量化操作後執行反量化操作。

量化數據表示。浮點數轉8位表示,是壓縮問題。權重、經過激活函數處理上層輸出,是分佈在一個範圍內的值。量化過程,找出最大值、最小值,將浮點數線性分佈,做線性擴展。

優化矩陣乘法運算。谷歌開源小型獨立低精度通用矩陣乘法(General Matrix to Matrix Multiplication,GEMM)庫 gemmlowp。https://github.com/google/gemmlowp 。

iOS系統實踐。

環境準備。操作系統Mac OS X,集成開發工具Xcode 7.3以上版本。編譯TensorFlow核心靜態庫。tensorflow/contrib/makefiles/download_depencies.sh 。依賴庫下載到tensorflow/contrib/makefile/downloads目錄。eigen #C++開源矩陣計算工具。gemmlowp #小型獨立低精度通用矩陣乘法(GEMM)庫。googletest #谷歌開源C++測試框架。protobuf #谷歌開源數據交換格式協議。re2 #谷歌開源正則表達式庫。

編譯演示程度,運行。tensorflow/contrib/makefile/build_all_iso.sh。編譯生成靜態庫,tensorflow/contrib/makefile/gen/lib:ios_ARM64、ios_ARMV7、ios_ARMV7S、ios_I386、ios_X86_64、libtensorflow-core.a。Xcode模擬器或iOS設備運行APP預測示例。TensorFlow iOS示例。https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/ 。3個目錄。benchmark目錄是預測基準示例。simple目錄是圖片預測示例。camera目錄是視頻流實時預測示例。下載Inception V1模型,能識別1000類圖片,https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip 。解壓模型,複製到benchmark、simple、camera的data目錄。運行目錄下xcodeproj文件。選擇iPhone 7 Plus模擬器,點擊運行標誌,編譯完成點擊Run Model按鈕。預測結果見Xcode 控制台。

自定義模型編譯、運行。https://github.com/tensorflow/tensorflow/blob/15b1cf025da5c6ac2bcf4d4878ee222fca3aec4a/tensorflow/docs_src/tutorials/image_retraining.md 。下載花卉數據 http://download.tensorflow.org/example_images/flower_photos.tgz 。鬱金香(tulips)、玫瑰(roses)、浦公英(dandelion)、嚮日葵(sunflowers)、雛菊(daisy)5種花卉文件目錄,各800張圖片。
訓練原始模型。下載預訓練Inception V3模型 http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz 。

python tensorflow/examples/image_retraining/retrain.py \
--bottlenectk_dir=/tmp/bottlenecks/ \
--how_many_training_steps 10 \
--model_dir=/tmp/inception \
--output_graph=/tmp/retrained_graph.pb \
--output_labels=/tmp/retrained_labels.txt \
--image_dir /tmp/flower_photos

訓練完成,/tmp目錄有模型文件retrained_graph.pb、標簽文件上retrained_labels.txt。“瓶頸”(bottlenecks)文件,描述實際分類最終輸出層前一層(倒數第二層)。倒數第二層訓練很好,瓶頸值是有意義緊湊圖像摘要,包含足夠信息使分類選擇。第一次訓練,retrain.py文件代碼先分析所有圖片,計算每張圖片瓶頸值存儲下來。每張圖片被使用多次,不必重覆計算。

編譯iOS支持模型。https://petewarden.com/2016/09/27/tensorflow-for-mobile-poets/ 。原始模型到iOS模型,先去掉iOS系統不支持操作,優化模型,再將模型量化,權重變8位常數,縮小模型,最後模型記憶體映射。
去掉iOS系統不支持操作,優化模型。iOS版本TensorFlow僅支持預測階段常見沒有大外部依賴關係操作。支持操作列表:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/makefile/tf_op_files.txt 。DecodeJpeg不支持,JPEG格式圖片解碼,依賴libjpeg。從攝像頭實時識別花卉種類,直接處理相機圖像緩衝區,不存JPEG文件再解碼。預訓練模型Inception V3 從圖片數據集訓練,包含DecodeJpeg操作。輸入數據直接提供(feed)Decode後Mul操作,繞過Decode操作。優化加速預測,顯式批處理規範化(explicit batch normalization)操作合併到捲積權重,減少計算次數。

bazel build tensorflow/python/tools:optimize_for_inference
bazel-bin/tensorflow/python/tools/optimize_for_inference \
--input=/tmp/retrained_graph.pb \
--output=/tmp/optimized_graph.pb \
--input_names=Mul \
--output_names=final_result \

label_image命令預測:

bazel-bin/tensorflow/examples/label_image/label_image \
--output_layer=final_result \
--labels=/tmp/output_labels.txt \
--image=/tmp/flower_photos/daisy/5547758_eea9edfd54_n.jpg
--graph=/tmp/output_graph.pb \
--input_layer=Mul \
--input_mean=128 \
--input_std=128 \

量化模型。蘋果系統在.ipa包分發應用程度,所有應用程度資源都用zip壓縮。模型權重從浮點數轉整數(範圍0~255),損失準確度,小於1%。

bazel build tensorflow/tools/quantization:quantization_graph
bazel-bin/tensorflow/tools/quantization/quantization_graph \
--input=/tmp/optimized_graph.pb \
--output=/tmp/rounded_graph.pb \
--output_node_names=final_result \
--mode=weights_rounded

記憶體映射 memory mapping。物理記憶體映射到進程地址空間內,應用程式直接用輸入/輸出地址空間,提高讀寫效率。模型全部一次性載入到記憶體緩衝區,會對iOS RAM施加過大壓力,操作系統會殺死記憶體占用過多程式。模型權值緩衝區只讀,可映射到記憶體。重新排列模型,權重分部分逐塊從主GraphDef載入到記憶體。

bazel build tensorflow/contrib/util:convert_graphdef_memmapped_format
bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format \
--in_graph=/tmp/rounded_graph.pb \
--out_graph=/tmp/mmapped_graph.pb

生成iOS工程文件運行。視頻流實進預測演示程式例子。https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/camera 。模型文件、標記文件複製到data目錄。修改CameraExampleViewController.mm,更改載入模型文件名稱、輸入圖片尺寸、操作節點名字、縮放像素大小。

#import <AssertMacros.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <CoreImage/CoreImage.h>
#import <ImageIO/ImageIO.h>
#import "CameraExampleViewController.h"
#include <sys/time.h>
#include "tensorflow_utils.h"
// 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 =299;// 224;
const int wanted_input_height = 299;//224;
const int wanted_input_channels = 3;
const float input_mean = 128.0f;//117.0f;
const float input_std = 128.0f;//1.0f;
const std::string input_layer_name = "Mul";//"input";
const std::string output_layer_name = "final_result";//"softmax1";
static void *AVCaptureStillImageIsCapturingStillImageContext =
    &AVCaptureStillImageIsCapturingStillImageContext;
@interface CameraExampleViewController (InternalMethods)
- (void)setupAVCapture;
- (void)teardownAVCapture;
@end
@implementation CameraExampleViewController
- (void)setupAVCapture {
  NSError *error = nil;
  session = [AVCaptureSession new];
  if ([[UIDevice currentDevice] userInterfaceIdiom] ==
      UIUserInterfaceIdiomPhone)
    [session setSessionPreset:AVCaptureSessionPreset640x480];
  else
    [session setSessionPreset:AVCaptureSessionPresetPhoto];
  AVCaptureDevice *device =
      [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  AVCaptureDeviceInput *deviceInput =
      [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
  assert(error == nil);
  isUsingFrontFacingCamera = NO;
  if ([session canAddInput:deviceInput]) [session addInput:deviceInput];
  stillImageOutput = [AVCaptureStillImageOutput new];
  [stillImageOutput
      addObserver:self
       forKeyPath:@"capturingStillImage"
          options:NSKeyValueObservingOptionNew
          context:(void *)(AVCaptureStillImageIsCapturingStillImageContext)];
  if ([session canAddOutput:stillImageOutput])
    [session addOutput:stillImageOutput];
  videoDataOutput = [AVCaptureVideoDataOutput new];
  NSDictionary *rgbOutputSettings = [NSDictionary
      dictionaryWithObject:[NSNumber numberWithInt:kCMPixelFormat_32BGRA]
                    forKey:(id)kCVPixelBufferPixelFormatTypeKey];
  [videoDataOutput setVideoSettings:rgbOutputSettings];
  [videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
  videoDataOutputQueue =
      dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
  [videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
  if ([session canAddOutput:videoDataOutput])
    [session addOutput:videoDataOutput];
  [[videoDataOutput connectionWithMediaType:AVMediaTypeVideo] setEnabled:YES];
  previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
  [previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
  [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
  CALayer *rootLayer = [previewView layer];
  [rootLayer setMasksToBounds:YES];
  [previewLayer setFrame:[rootLayer bounds]];
  [rootLayer addSublayer:previewLayer];
  [session startRunning];
  if (error) {
    NSString *title = [NSString stringWithFormat:@"Failed with error %d", (int)[error code]];
    UIAlertController *alertController =
        [UIAlertController alertControllerWithTitle:title
                                            message:[error localizedDescription]
                                     preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *dismiss =
        [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:dismiss];
    [self presentViewController:alertController animated:YES completion:nil];
    [self teardownAVCapture];
  }
}
- (void)teardownAVCapture {
  [stillImageOutput removeObserver:self forKeyPath:@"isCapturingStillImage"];
  [previewLayer removeFromSuperlayer];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
  if (context == AVCaptureStillImageIsCapturingStillImageContext) {
    BOOL isCapturingStillImage =
        [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
    if (isCapturingStillImage) {
      // do flash bulb like animation
      flashView = [[UIView alloc] initWithFrame:[previewView frame]];
      [flashView setBackgroundColor:[UIColor whiteColor]];
      [flashView setAlpha:0.f];
      [[[self view] window] addSubview:flashView];
      [UIView animateWithDuration:.4f
                       animations:^{
                         [flashView setAlpha:1.f];
                       }];
    } else {
      [UIView animateWithDuration:.4f
          animations:^{
            [flashView setAlpha:0.f];
          }
          completion:^(BOOL finished) {
           [flashView removeFromSuperview];
            flashView = nil;
          }];
    }
  }
}
- (AVCaptureVideoOrientation)avOrientationForDeviceOrientation:
    (UIDeviceOrientation)deviceOrientation {
  AVCaptureVideoOrientation result =
      (AVCaptureVideoOrientation)(deviceOrientation);
  if (deviceOrientation == UIDeviceOrientationLandscapeLeft)
    result = AVCaptureVideoOrientationLandscapeRight;
  else if (deviceOrientation == UIDeviceOrientationLandscapeRight)
    result = AVCaptureVideoOrientationLandscapeLeft;
  return result;
}
- (IBAction)takePicture:(id)sender {
  if ([session isRunning]) {
    [session stopRunning];
    [sender setTitle:@"Continue" forState:UIControlStateNormal];
    flashView = [[UIView alloc] initWithFrame:[previewView frame]];
    [flashView setBackgroundColor:[UIColor whiteColor]];
    [flashView setAlpha:0.f];
    [[[self view] window] addSubview:flashView];
    [UIView animateWithDuration:.2f
        animations:^{
          [flashView setAlpha:1.f];
        }
        completion:^(BOOL finished) {
          [UIView animateWithDuration:.2f
              animations:^{
                [flashView setAlpha:0.f];
              }
              completion:^(BOOL finished) {
                [flashView removeFromSuperview];
                flashView = nil;
              }];
        }];
  } else {
    [session startRunning];
    [sender setTitle:@"Freeze Frame" forState:UIControlStateNormal];
  }
}
+ (CGRect)videoPreviewBoxForGravity:(NSString *)gravity
                          frameSize:(CGSize)frameSize
                       apertureSize:(CGSize)apertureSize {
  CGFloat apertureRatio = apertureSize.height / apertureSize.width;
  CGFloat viewRatio = frameSize.width / frameSize.height;
  CGSize size = CGSizeZero;
  if ([gravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
    if (viewRatio > apertureRatio) {
      size.width = frameSize.width;
      size.height =
          apertureSize.width * (frameSize.width / apertureSize.height);
    } else {
      size.width =
          apertureSize.height * (frameSize.height / apertureSize.width);
      size.height = frameSize.height;
   }
  } else if ([gravity isEqualToString:AVLayerVideoGravityResizeAspect]) {
    if (viewRatio > apertureRatio) {
      size.width =
          apertureSize.height * (frameSize.height / apertureSize.width);
      size.height = frameSize.height;
    } else {
      size.width = frameSize.width;
      size.height =
          apertureSize.width * (frameSize.width / apertureSize.height);
    }
  } else if ([gravity isEqualToString:AVLayerVideoGravityResize]) {
    size.width = frameSize.width;
    size.height = frameSize.height;
  }
  CGRect videoBox;
  videoBox.size = size;
  if (size.width < frameSize.width)
    videoBox.origin.x = (frameSize.width - size.width) / 2;
  else
    videoBox.origin.x = (size.width - frameSize.width) / 2;
  if (size.height < frameSize.height)
    videoBox.origin.y = (frameSize.height - size.height) / 2;
  else
    videoBox.origin.y = (size.height - frameSize.height) / 2;
  return videoBox;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  CFRetain(pixelBuffer);
  [self runCNNOnFrame:pixelBuffer];
  CFRelease(pixelBuffer);
}
- (void)runCNNOnFrame:(CVPixelBufferRef)pixelBuffer {
  assert(pixelBuffer != NULL);
  OSType sourcePixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
  int doReverseChannels;
  if (kCVPixelFormatType_32ARGB == sourcePixelFormat) {
    doReverseChannels = 1;
  } else if (kCVPixelFormatType_32BGRA == sourcePixelFormat) {
    doReverseChannels = 0;
  } else {
    assert(false);  // Unknown source format
  }
  const int sourceRowBytes = (int)CVPixelBufferGetBytesPerRow(pixelBuffer);
  const int image_width = (int)CVPixelBufferGetWidth(pixelBuffer);
  const int fullHeight = (int)CVPixelBufferGetHeight(pixelBuffer);
  CVPixelBufferLockFlags unlockFlags = kNilOptions;
  CVPixelBufferLockBaseAddress(pixelBuffer, unlockFlags);
  unsigned char *sourceBaseAddr =
      (unsigned char *)(CVPixelBufferGetBaseAddress(pixelBuffer));
  int image_height;
  unsigned char *sourceStartAddr;
  if (fullHeight <= image_width) {
    image_height = fullHeight;
    sourceStartAddr = sourceBaseAddr;
  } else {
    image_height = image_width;
    const int marginY = ((fullHeight - image_width) / 2);
    sourceStartAddr = (sourceBaseAddr + (marginY * sourceRowBytes));
  }
  const int image_channels = 4;
  assert(image_channels >= wanted_input_channels);
  tensorflow::Tensor image_tensor(
      tensorflow::DT_FLOAT,
      tensorflow::TensorShape(
          {1, wanted_input_height, wanted_input_width, wanted_input_channels}));
  auto image_tensor_mapped = image_tensor.tensor<float, 4>();
  tensorflow::uint8 *in = sourceStartAddr;
  float *out = image_tensor_mapped.data();
  for (int y = 0; y < wanted_input_height; ++y) {
    float *out_row = out + (y * wanted_input_width * wanted_input_channels);
    for (int x = 0; x < wanted_input_width; ++x) {
      const int in_x = (y * image_width) / wanted_input_width;
      const int in_y = (x * image_height) / wanted_input_height;
      tensorflow::uint8 *in_pixel =
          in + (in_y * image_width * image_channels) + (in_x * image_channels);
      float *out_pixel = out_row + (x * wanted_input_channels);
      for (int c = 0; c < wanted_input_channels; ++c) {
        out_pixel[c] = (in_pixel[c] - input_mean) / input_std;
      }
    }
  }
  CVPixelBufferUnlockBaseAddress(pixelBuffer, unlockFlags);
  if (tf_session.get()) {
    std::vector<tensorflow::Tensor> outputs;
    tensorflow::Status run_status = tf_session->Run(
        {{input_layer_name, image_tensor}}, {output_layer_name}, {}, &outputs);
    if (!run_status.ok()) {
      LOG(ERROR) << "Running model failed:" << run_status;
    } else {
      tensorflow::Tensor *output = &outputs[0];
      auto predictions = output->flat<float>();
      NSMutableDictionary *newValues = [NSMutableDictionary dictionary];
      for (int index = 0; index < predictions.size(); index += 1) {
        const float predictionValue = predictions(index);
        if (predictionValue > 0.05f) {
          std::string label = labels[index % predictions.size()];
          NSString *labelObject = [NSString stringWithUTF8String:label.c_str()];
          NSNumber *valueObject = [NSNumber numberWithFloat:predictionValue];
          [newValues setObject:valueObject forKey:labelObject];
        }
      }
      dispatch_async(dispatch_get_main_queue(), ^(void) {
        [self setPredictionValues:newValues];
      });
    }
  }
  CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}
- (void)dealloc {
  [self teardownAVCapture];
}
// use front/back camera
- (IBAction)switchCameras:(id)sender {
  AVCaptureDevicePosition desiredPosition;
  if (isUsingFrontFacingCamera)
    desiredPosition = AVCaptureDevicePositionBack;
  else
    desiredPosition = AVCaptureDevicePositionFront;
  for (AVCaptureDevice *d in
       [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
    if ([d position] == desiredPosition) {
      [[previewLayer session] beginConfiguration];
      AVCaptureDeviceInput *input =
          [AVCaptureDeviceInput deviceInputWithDevice:d error:nil];
      for (AVCaptureInput *oldInput in [[previewLayer session] inputs]) {
        [[previewLayer session] removeInput:oldInput];
      }
      [[previewLayer session] addInput:input];
      [[previewLayer session] commitConfiguration];
      break;
    }
  }
  isUsingFrontFacingCamera = !isUsingFrontFacingCamera;
}
- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
}
- (void)viewDidLoad {
  [super viewDidLoad];
  square = [UIImage imageNamed:@"squarePNG"];
  synth = [[AVSpeechSynthesizer alloc] init];
  labelLayers = [[NSMutableArray alloc] init];
  oldPredictionValues = [[NSMutableDictionary alloc] init];
  tensorflow::Status load_status;
  if (model_uses_memory_mapping) {
    load_status = LoadMemoryMappedModel(
        model_file_name, model_file_type, &tf_session, &tf_memmapped_env);
  } else {
    load_status = LoadModel(model_file_name, model_file_type, &tf_session);
  }
  if (!load_status.ok()) {
    LOG(FATAL) << "Couldn't load model: " << load_status;
  }
  tensorflow::Status labels_status =
      LoadLabels(labels_file_name, labels_file_type, &labels);
  if (!labels_status.ok()) {
    LOG(FATAL) << "Couldn't load labels: " << labels_status;
  }
  [self setupAVCapture];
}
- (void)viewDidUnload {
  [super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
  [super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation {
  return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)prefersStatusBarHidden {
  return YES;
}
- (void)setPredictionValues:(NSDictionary *)newValues {
  const float decayValue = 0.75f;
  const float updateValue = 0.25f;
  const float minimumThreshold = 0.01f;
  NSMutableDictionary *decayedPredictionValues =
      [[NSMutableDictionary alloc] init];
  for (NSString *label in oldPredictionValues) {
    NSNumber *oldPredictionValueObject =
        [oldPredictionValues objectForKey:label];
    const float oldPredictionValue = [oldPredictionValueObject floatValue];
    const float decayedPredictionValue = (oldPredictionValue * decayValue);
    if (decayedPredictionValue > minimumThreshold) {
      NSNumber *decayedPredictionValueObject =
          [NSNumber numberWithFloat:decayedPredictionValue];
      [decayedPredictionValues setObject:decayedPredictionValueObject
                                  forKey:label];
    }
  }
  oldPredictionValues = decayedPredictionValues;
  for (NSString *label in newValues) {
    NSNumber *newPredictionValueObject = [newValues objectForKey:label];
    NSNumber *oldPredictionValueObject =
        [oldPredictionValues objectForKey:label];
    if (!oldPredictionValueObject) {
      oldPredictionValueObject = [NSNumber numberWithFloat:0.0f];
    }
    const float newPredictionValue = [newPredictionValueObject floatValue];
    const float oldPredictionValue = [oldPredictionValueObject floatValue];
    const float updatedPredictionValue =
        (oldPredictionValue + (newPredictionValue * updateValue));
    NSNumber *updatedPredictionValueObject =
        [NSNumber numberWithFloat:updatedPredictionValue];
    [oldPredictionValues setObject:updatedPredictionValueObject forKey:label];
  }
  NSArray *candidateLabels = [NSMutableArray array];
  for (NSString *label in oldPredictionValues) {
    NSNumber *oldPredictionValueObject =
        [oldPredictionValues objectForKey:label];
    const float oldPredictionValue = [oldPredictionValueObject floatValue];
    if (oldPredictionValue > 0.05f) {
      NSDictionary *entry = @{
        @"label" : label,
        @"value" : oldPredictionValueObject
      };
      candidateLabels = [candidateLabels arrayByAddingObject:entry];
    }
  }
  NSSortDescriptor *sort =
      [NSSortDescriptor sortDescriptorWithKey:@"value" ascending:NO];
  NSArray *sortedLabels = [candidateLabels
      sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
  const float leftMargin = 10.0f;
  const float topMargin = 10.0f;
  const float valueWidth = 48.0f;
  const float valueHeight = 26.0f;
  const float labelWidth = 246.0f;
  const float labelHeight = 26.0f;
  const float labelMarginX = 5.0f;
  const float labelMarginY = 5.0f;
  [self removeAllLabelLayers];
  int labelCount = 0;
  for (NSDictionary *entry in sortedLabels) {
    NSString *label = [entry objectForKey:@"label"];
    NSNumber *valueObject = [entry objectForKey:@"value"];
    const float value = [valueObject floatValue];
    const float originY =
        (topMargin + ((labelHeight + labelMarginY) * labelCount));
    const int valuePercentage = (int)roundf(value * 100.0f);
    const float valueOriginX = leftMargin;
    NSString *valueText = [NSString stringWithFormat:@"%d%%", valuePercentage];
    [self addLabelLayerWithText:valueText
                        originX:valueOriginX
                        originY:originY
                          width:valueWidth
                         height:valueHeight
                      alignment:kCAAlignmentRight];
    const float labelOriginX = (leftMargin + valueWidth + labelMarginX);
    [self addLabelLayerWithText:[label capitalizedString]
                        originX:labelOriginX
                        originY:originY
                          width:labelWidth
                         height:labelHeight
                      alignment:kCAAlignmentLeft];
    if ((labelCount == 0) && (value > 0.5f)) {
      [self speak:[label capitalizedString]];
    }
    labelCount += 1;
    if (labelCount > 4) {
      break;
    }
  }
}
- (void)removeAllLabelLayers {
  for (CATextLayer *layer in labelLayers) {
    [layer removeFromSuperlayer];
  }
  [labelLayers removeAllObjects];
}
- (void)addLabelLayerWithText:(NSString *)text
                      originX:(float)originX
                      originY:(float)originY
                        width:(float)width
                       height:(float)height
                    alignment:(NSString *)alignment {
  CFTypeRef font = (CFTypeRef) @"Menlo-Regular";
  const float fontSize = 20.0f;
  const float marginSizeX = 5.0f;
  const float marginSizeY = 2.0f;
  const CGRect backgroundBounds = CGRectMake(originX, originY, width, height);
  const CGRect textBounds =
      CGRectMake((originX + marginSizeX), (originY + marginSizeY),
                 (width - (marginSizeX * 2)), (height - (marginSizeY * 2)));
  CATextLayer *background = [CATextLayer layer];
  [background setBackgroundColor:[UIColor blackColor].CGColor];
  [background setOpacity:0.5f];
  [background setFrame:backgroundBounds];
  background.cornerRadius = 5.0f;
  [[self.view layer] addSublayer:background];
  [labelLayers addObject:background];
  CATextLayer *layer = [CATextLayer layer];
  [layer setForegroundColor:[UIColor whiteColor].CGColor];
  [layer setFrame:textBounds];
  [layer setAlignmentMode:alignment];
  [layer setWrapped:YES];
  [layer setFont:font];
  [layer setFontSize:fontSize];
  layer.contentsScale = [[UIScreen mainScreen] scale];
  [layer setString:text];
  [[self.view layer] addSublayer:layer];
  [labelLayers addObject:layer];
}
- (void)setPredictionText:(NSString *)text withDuration:(float)duration {
  if (duration > 0.0) {
    CABasicAnimation *colorAnimation =
        [CABasicAnimation animationWithKeyPath:@"foregroundColor"];
    colorAnimation.duration = duration;
    colorAnimation.fillMode = kCAFillModeForwards;
    colorAnimation.removedOnCompletion = NO;
    colorAnimation.fromValue = (id)[UIColor darkGrayColor].CGColor;
    colorAnimation.toValue = (id)[UIColor whiteColor].CGColor;
    colorAnimation.timingFunction =
        [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    [self.predictionTextLayer addAnimation:colorAnimation
                                    forKey:@"colorAnimation"];
  } else {
    self.predictionTextLayer.foregroundColor = [UIColor whiteColor].CGColor;
  }
  [self.predictionTextLayer removeFromSuperlayer];
  [[self.view layer] addSublayer:self.predictionTextLayer];
  [self.predictionTextLayer setString:text];
}
- (void)speak:(NSString *)words {
  if ([synth isSpeaking]) {
    return;
  }
  AVSpeechUtterance *utterance =
      [AVSpeechUtterance speechUtteranceWithString:words];
  utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"];
  utterance.rate = 0.75 * AVSpeechUtteranceDefaultSpeechRate;
  [synth speakUtterance:utterance];
}
@end

 

連上iPhone手機,雙擊tensorflow/contrib/ios_examples/camera/camera_example.xcodeproj編譯運行。手機安裝好APP,打開APP,找到玫瑰花識別。訓練迭代次數10000次後,識別率99%以上。模擬器打包,生成打包工程文件位於/Users/libinggen/Library/Developer/Xcode/DeriveData/camera_example-dhfdsdfesfmrwtfb1fpfkfjsdfhdskf/Build/Products/Debug-iphoneos。打開CameraExample.app,有可執行文件CameraExample、資源文件模型文件mmapped_graph.pb、標記文件retrained_labels.txt。

Android系統實踐。

環境準備。MacBook Pro。Oracle官網下載JDK1.8版本。http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 。jdk-8u111-macosx-x64.dmg。雙擊安裝。設置Java環境變數:

JAVA_HOME='/usr/libexec/java_home'
export JAVA_HOME

搭建Android SDK環境。Android官網下載Android SDK,https://developer.android.com 。25.0.2版本。android-sdk_r25.0.2-macosx.zip。解壓到~/Library/Android/sdk目錄。build-tools、extras、patcher、platform-tools #各版本SDK 根據API Level劃分SDK版本、platforms、sources、system-images、temp #臨時文件夾 在SDK更新安裝時用到、tools #各版本通用SDK工具 有adb、aapt、aidl、dx文件。
搭建Android NDK環境。Android官網下載Android NDK Mac OS X版本,https://developer.android.com/ndk/downloads/index.html 。android-ndk-r13b-darwin-x86_64.zip文件。解壓,CHANGELOG.md、build、ndk-build、ndk-depends、ndk-gdb、ndk-stack、ndk-which、platforms、prebuilt、python-packages、shader-tools、simpleperf、source.properties、sources、toolchains。搭建Bazel。brew安裝bazel:

brew install bazel

更新bazel:

brew upgrade bazel

編譯演示程式運行。修改tensorflow-1.1.0根目錄WORKSPACE文件。android_sdk_repository、android_ndk_repository配置改為用戶自己安裝目錄、版本。

android_sdk_repository(
    name = "androidsdk",
    api_level = 25,
    build_tools_version = "25.0.2",
    # Replace with path to Android SDK on your system
    path = "~/Library/Android/sdk"
)
android_ndk_repository(
    name = "androidndk",
    api_level = 23,
    path = "~/Downloads/android-ndk-r13b"
)

 

在根目錄用bazel構建:

bazel build // tensorflow/examples/android:tensorflow_demo

編譯成功,預設在tensorflow-1.1.0/bazel-bin/tensorflow/examples/android目錄生成TensorFlow演示程式。
運行。生成apk文件傳輸到手機,手機攝像頭看效果。Android 6.0.1。開啟“開發者模式”。手機用數據線與電腦相連,進入SDK所在目錄,進入platform-tools文件夾,找到adb命令,執行:

./adb install tensorflow-0.12/bazel-bin/tensorflow/examples/android/tensorflow_demo.apk

tensorflow_demo.apk自動安裝到手機。打開TF Detec App。App 調起手機攝像頭,攝像頭返回數據流實時監測。

自定義模型編譯運行。訓練原始模型、編譯Android系統支持模型、生成Android apk文件運行。
訓練原始模型、編譯Android系統支持模型。用項目根目錄tensorflow/python/tools/optimize_for_inference.py、tensorflow/tools/quantization/quantize_graph.py、tensorflow/contrib/util/convert_graphdef_memmapped_format.cc對模型優化。將第一步生成原始模型文件retrained_graph.pb、標記文件retrained_labels.txt放在tensorflow/examples/android/assets目錄。修改tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowImageClassifier.java要載入模型文件名稱,輸入圖片尺寸、操作節點名字、縮放像素大小。

package org.tensorflow.demo;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.Trace;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Vector;
import org.tensorflow.Operation;
import org.tensorflow.contrib.android.TensorFlowInferenceInterface;
/** A classifier specialized to label images using TensorFlow. */
public class TensorFlowImageClassifier implements Classifier {
  private static final String TAG = "TensorFlowImageClassifier";
  // Only return this many results with at least this confidence.
  private static final int MAX_RESULTS = 3;
  private static final float THRESHOLD = 0.1f;
  // Config values.
  private String inputName;
  private String outputName;
  private int inputSize;
  private int imageMean;
  private float imageStd;
  // Pre-allocated buffers.
  private Vector<String> labels = new Vector<String>();
  private int[] intValues;
  
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、佈局文件:res/drawable/bg_shadow.xml [java] view plain copy <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/ ...
  • 頭文件 授權 關於通訊錄的授權這裡不再多說了,我在上一篇中有提到:通訊錄授權 訪問通訊錄 跳轉到系統通訊錄 添加代理 代理方法 註:上面兩個選擇回調代理實現一個就可以 相關借鑒:http://www.jb51.net/article/95500.htm ...
  • 總結下幾個常用到的獲取手機許可權,從iOS8以後,獲取手機某種許可權需要在info.plist文件中添加許可權的描述文件 1.通訊錄 頭文件 獲取通訊錄許可權並請求授權 2.相機 頭文件 獲取相機許可權(直接跳相機,在跳到相機時,會提示是否允許訪問相機) 3.相冊 頭文件 獲取相冊許可權(直接跳相冊,在跳到相冊 ...
  • 為什麼要對Android中的圖片進行採樣縮放呢? 是為了更加高效的載入Bitmap。假設通過imageView來顯示圖片,很多時候ImageView並沒有圖片的原始尺寸那麼大,這時候把整張圖片載入進來後再設給ImageView是沒有必要的,因為ImagView並沒有辦法顯示原始的圖片。 所以我們可以 ...
  • “移動警務通”綜合系統為公安行業提供一種高效的移動警務應用解決方案,適用於交警、巡警、刑警、治安警等各類警務人員,利用移動網路,通過身份證拍照識別軟體接入警務通後臺管理系統和警務通呼叫中心(110指揮調度中心/便民報警處理中心)系統,構成新一代立體的公安辦公網路。目前身份證拍照識別、車牌拍照識別技術 ...
  • 在管理工具里打開Internet 信息服務(IIS)管理器。然後選擇需要配置的網站。 右側的界面中會顯示該網站的所有功能配置,我們選擇並點擊進入“MIME類型” 在左側的操作區選擇點擊“添加”MIME。 在彈出的添加視窗里的文件擴展名輸入:APK 在MIME類型輸入:application/vnd. ...
  • 微信分享前提: 1.需要成功在微信開發者平臺註冊了賬號, 並取的對應的 appkey appSecret。 2. 針對iOS9 添加了微信的白名單,以及設置了 scheme url 。 這都可以參照上面的鏈接,進行設置好。 3. 分享不跳轉的時候原因總結, 具體方法如下: 1. 首先檢查下是否有向微 ...
  • 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自 "【趙彥軍的博客】" 在前面我介紹了插件開發的基本流程 "【Android Studio 插件開發詳解一:入門練手】" "【Android Studio 插件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...