Come migliorare la precisione della demo della fotocamera Tensorflow su iOS per il grafico di riqualificazione

Ho un’app per Android che è stata modellata sulla demo di Tensorflow per Android per la classificazione delle immagini,

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android

L’app originale utilizza un file di grafico tensorflow (.pb) per classificare un insieme generico di immagini da Inception v3 (credo)

Ho quindi formato il mio grafico per le mie immagini seguendo le istruzioni nel blog di Tensorflow for Poets,

TensorFlow for Poets

e questo ha funzionato molto bene nell’app Android, dopo aver cambiato le impostazioni in,

ClassifierActivity

private static final int INPUT_SIZE = 299; private static final int IMAGE_MEAN = 128; private static final float IMAGE_STD = 128.0f; private static final String INPUT_NAME = "Mul"; private static final String OUTPUT_NAME = "final_result"; private static final String MODEL_FILE = "file:///android_asset/optimized_graph.pb"; private static final String LABEL_FILE = "file:///android_asset/retrained_labels.txt"; 

Per trasferire l’app su iOS, ho quindi utilizzato la demo della videocamera iOS, https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/camera

e usato lo stesso file grafico e cambiato le impostazioni in,

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 = 299; const int wanted_input_height = 299; const int wanted_input_channels = 3; const float input_mean = 128f; const float input_std = 128.0f; const std::string input_layer_name = "Mul"; const std::string output_layer_name = "final_result"; 

Dopo questo l’app funziona su iOS, tuttavia …

L’app su Android funziona molto meglio di iOS nel rilevare le immagini classificate. Se riempio la porta di visualizzazione della fotocamera con l’immagine, entrambe si comportano allo stesso modo. Ma normalmente l’immagine da rilevare è solo una parte della porta di visualizzazione della videocamera, su Android questo non sembra avere un impatto molto, ma su iOS ha un impatto molto, quindi iOS non può classificare l’immagine.

La mia ipotesi è che Android stia ritagliando se la videocamera visualizza la porta nell’area centrale 299×299, dove iOS sta ridimensionando la sua porta di visualizzazione della telecamera nell’area centrale 299×299.

Qualcuno può confermarlo? e qualcuno sa come risolvere la demo di iOS per individuare meglio le immagini focalizzate? (fallo ritagliare)

Nella demo di Android,

ClassifierActivity.onPreviewSizeChosen ()

 rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); croppedBitmap = Bitmap.createBitmap(INPUT_SIZE, INPUT_SIZE, Config.ARGB_8888); frameToCropTransform = ImageUtils.getTransformationMatrix( previewWidth, previewHeight, INPUT_SIZE, INPUT_SIZE, sensorOrientation, MAINTAIN_ASPECT); cropToFrameTransform = new Matrix(); frameToCropTransform.invert(cropToFrameTransform); 

e su iOS è ha,

CameraExampleViewController.runCNNOnFrame ()

 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 = 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(); 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); 

Penso che il problema sia qui,

 tensorflow::uint8 *in_pixel = in + (in_y * image_width * image_channels) + (in_x * image_channels); float *out_pixel = out_row + (x * wanted_input_channels); 

La mia comprensione è che è sufficiente ridimensionare la dimensione 299 scegliendo ogni x pixel anziché ridimensionare l’immagine originale alla dimensione 299. Quindi questo porta a uno scarso ridimensionamento e un scarso riconoscimento delle immagini.

La soluzione è di prima scala su pixelBuffer fino alla dimensione 299. Ho provato questo,

 UIImage *uiImage = [self uiImageFromPixelBuffer: pixelBuffer]; float scaleFactor = (float)wanted_input_height / (float)fullHeight; float newWidth = image_width * scaleFactor; NSLog(@"width: %d, height: %d, scale: %f, height: %f", image_width, fullHeight, scaleFactor, newWidth); CGSize size = CGSizeMake(wanted_input_width, wanted_input_height); UIGraphicsBeginImageContext(size); [uiImage drawInRect:CGRectMake(0, 0, newWidth, size.height)]; UIImage *destImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); pixelBuffer = [self pixelBufferFromCGImage: destImage.CGImage]; 

e per convertire l’immagine in un buffer pixle,

 - (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image { NSDictionary *options = @{ (NSString*)kCVPixelBufferCGImageCompatibilityKey : @YES, (NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES, }; CVPixelBufferRef pxbuffer = NULL; CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, CGImageGetWidth(image), CGImageGetHeight(image), kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); if (status!=kCVReturnSuccess) { NSLog(@"Operation failed"); } NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); CVPixelBufferLockBaseAddress(pxbuffer, 0); void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pxdata, CGImageGetWidth(image), CGImageGetHeight(image), 8, 4*CGImageGetWidth(image), rgbColorSpace, kCGImageAlphaNoneSkipFirst); NSParameterAssert(context); CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)); CGAffineTransform flipVertical = CGAffineTransformMake( 1, 0, 0, -1, 0, CGImageGetHeight(image) ); CGContextConcatCTM(context, flipVertical); CGAffineTransform flipHorizontal = CGAffineTransformMake( -1.0, 0.0, 0.0, 1.0, CGImageGetWidth(image), 0.0 ); CGContextConcatCTM(context, flipHorizontal); CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); CGColorSpaceRelease(rgbColorSpace); CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0); return pxbuffer; } - (UIImage*) uiImageFromPixelBuffer: (CVPixelBufferRef) pixelBuffer { CIImage *ciImage = [CIImage imageWithCVPixelBuffer: pixelBuffer]; CIContext *temporaryContext = [CIContext contextWithOptions:nil]; CGImageRef videoImage = [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))]; UIImage *uiImage = [UIImage imageWithCGImage:videoImage]; CGImageRelease(videoImage); return uiImage; } 

Non sono sicuro se questo è il modo migliore per ridimensionare, ma questo ha funzionato. Ma sembrava rendere la classificazione delle immagini ancora peggiore, non meglio …

Qualche idea o problema con la conversione / ridimensionamento dell’immagine?

Solutions Collecting From Web of "Come migliorare la precisione della demo della fotocamera Tensorflow su iOS per il grafico di riqualificazione"

Dato che non stai usando il rilevatore YOLO, il flag MAINTAIN_ASPECT è impostato su false . Quindi l’immagine sull’app per Android non viene ritagliata, ma è ridimensionata. Tuttavia, nello snippet di codice fornito non vedo l’effettiva inizializzazione del flag. Conferma che il valore del flag è effettivamente false nella tua app.

So che questa non è una soluzione completa, ma spero che questo ti aiuti a risolvere il problema.

Il rilevamento dell’object Tensorflow ha configurazioni standard e predefinite, di seguito è riportato l’elenco delle impostazioni,

Cose importanti da verificare in base al modello ML di input,

-> model_file_name – Questo in base al nome del file .pb,

-> model_uses_memory_mapping – Sta a te ridurre l’utilizzo complessivo della memoria.

-> labels_file_name – Varia in base al nome del file dell’etichetta,

-> input_layer_name / output_layer_name – Assicurati di utilizzare i tuoi nomi di input / output del layer che stai utilizzando durante la creazione di un file grafico (.pb).

snippet :

 // 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 = @"graph";//@"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 = true; // If you have your own model, point this to the labels file. static NSString* labels_file_name = @"labels";//@"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 = "final_result"; 

Rilevamento Tensorflow immagine personalizzata, puoi utilizzare lo snippet di lavoro seguente:

-> Per questo processo devi solo passare l’object UIImage.CGImage,

 NSString* RunInferenceOnImageResult(CGImageRef image) { tensorflow::SessionOptions options; tensorflow::Session* session_pointer = nullptr; tensorflow::Status session_status = tensorflow::NewSession(options, &session_pointer); if (!session_status.ok()) { std::string status_string = session_status.ToString(); return [NSString stringWithFormat: @"Session create failed - %s", status_string.c_str()]; } std::unique_ptr session(session_pointer); LOG(INFO) < < "Session created."; tensorflow::GraphDef tensorflow_graph; LOG(INFO) << "Graph created."; NSString* network_path = FilePathForResourceNames(@"tensorflow_inception_graph", @"pb"); PortableReadFileToProtol([network_path UTF8String], &tensorflow_graph); LOG(INFO) << "Creating session."; tensorflow::Status s = session->Create(tensorflow_graph); if (!s.ok()) { LOG(ERROR) < < "Could not create TensorFlow Graph: " << s; return @""; } // Read the label list NSString* labels_path = FilePathForResourceNames(@"imagenet_comp_graph_label_strings", @"txt"); std::vector label_strings; std::ifstream t; t.open([labels_path UTF8String]); std::string line; while(t){ std::getline(t, line); label_strings.push_back(line); } t.close(); // Read the Grace Hopper image. //NSString* image_path = FilePathForResourceNames(@"grace_hopper", @"jpg"); int image_width; int image_height; int image_channels; // std::vector image_data = LoadImageFromFile( // [image_path UTF8String], &image_width, &image_height, &image_channels); std::vector image_data = LoadImageFromImage(image,&image_width, &image_height, &image_channels); 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; assert(image_channels >= wanted_channels); tensorflow::Tensor image_tensor( tensorflow::DT_FLOAT, tensorflow::TensorShape({ 1, wanted_height, wanted_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor(); tensorflow::uint8* in = image_data.data(); // tensorflow::uint8* in_end = (in + (image_height * image_width * image_channels)); float* out = image_tensor_mapped.data(); for (int y = 0; y < wanted_height; ++y) { const int in_y = (y * image_height) / wanted_height; tensorflow::uint8* in_row = in + (in_y * image_width * image_channels); float* out_row = out + (y * wanted_width * wanted_channels); for (int x = 0; x < wanted_width; ++x) { const int in_x = (x * image_width) / wanted_width; tensorflow::uint8* in_pixel = in_row + (in_x * image_channels); float* out_pixel = out_row + (x * wanted_channels); for (int c = 0; c < wanted_channels; ++c) { out_pixel[c] = (in_pixel[c] - input_mean) / input_std; } } } NSString* result; // result = [NSString stringWithFormat: @"%@ - %lu, %s - %dx%d", result, // label_strings.size(), label_strings[0].c_str(), image_width, image_height]; std::string input_layer = "input"; std::string output_layer = "output"; std::vector outputs; tensorflow::Status run_status = session->Run({{input_layer, image_tensor}}, {output_layer}, {}, &outputs); if (!run_status.ok()) { LOG(ERROR) < < "Running model failed: " << run_status; tensorflow::LogAllRegisteredKernels(); result = @"Error running model"; return result; } tensorflow::string status_string = run_status.ToString(); result = [NSString stringWithFormat: @"Status :%s\n", status_string.c_str()]; tensorflow::Tensor* output = &outputs[0]; const int kNumResults = 5; const float kThreshold = 0.1f; std::vector > top_results; GetTopN(output->flat(), kNumResults, kThreshold, &top_results); std::stringstream ss; ss.precision(3); for (const auto& result : top_results) { const float confidence = result.first; const int index = result.second; ss < < index << " " << confidence << " "; // Write out the result as a string if (index < label_strings.size()) { // just for safety: theoretically, the output is under 1000 unless there // is some numerical issues leading to a wrong prediction. ss << label_strings[index]; } else { ss << "Prediction: " << index; } ss << "\n"; } LOG(INFO) << "Predictions: " << ss.str(); tensorflow::string predictions = ss.str(); result = [NSString stringWithFormat: @"%@ - %s", result, predictions.c_str()]; return result; } 

Ridimensionamento dell'immagine per larghezza e altezza personalizzate - snippet di codice C ++,

 std::vector LoadImageFromImage(CGImageRef image, int* out_width, int* out_height, int* out_channels) { const int width = (int)CGImageGetWidth(image); const int height = (int)CGImageGetHeight(image); const int channels = 4; CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); const int bytes_per_row = (width * channels); const int bytes_in_image = (bytes_per_row * height); std::vector result(bytes_in_image); const int bits_per_component = 8; CGContextRef context = CGBitmapContextCreate(result.data(), width, height, bits_per_component, bytes_per_row, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(color_space); CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); CGContextRelease(context); CFRelease(image); *out_width = width; *out_height = height; *out_channels = channels; return result; } 

La funzione precedente ti aiuta a caricare i dati dell'immagine in base al tuo rapporto personalizzato. Il rapporto pixel ad alta precisione dell'immagine per larghezza e altezza durante la classificazione di tensorflow è 224 x 224.

È necessario richiamare la funzione LoadImage di RunInferenceOnImageResult, con argomenti di larghezza e altezza personalizzati e riferimenti alle immagini.

Per favore cambia questo codice:

 // 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; const int wanted_input_height = 299; const int wanted_input_channels = 3; const float input_mean = 128f; const float input_std = 1.0f; const std::string input_layer_name = "Mul"; const std::string output_layer_name = "final_result"; 

Qui cambia: const float input_std = 1.0f;