and android ios objective-c machine-learning tensorflow

android - and - Cómo mejorar la precisión de la demostración de la cámara Tensorflow en iOS para un gráfico renovado



tensorflow and android (3)

Como no está utilizando el Detector YOLO, el indicador MAINTAIN_ASPECT se establece en false . Por lo tanto, la imagen en la aplicación de Android no se recorta, pero se escala. Sin embargo, en el fragmento de código proporcionado no veo la inicialización real de la bandera. Confirme que el valor de la bandera es realmente false en su aplicación.

Sé que esta no es una solución completa, pero espero que esto le ayude a solucionar el problema.

Tengo una aplicación para Android que fue modelada después de la demostración de Tensorflow para clasificar imágenes,

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

La aplicación original utiliza un archivo de tensorflow graph (.pb) para clasificar un conjunto genérico de imágenes de Inception v3 (creo)

Luego entrené mi propia gráfica para mis propias imágenes siguiendo las instrucciones del blog Tensorflow for Poets,

https://petewarden.com/2016/02/28/tensorflow-for-poets/

y esto funcionó muy bien en la aplicación de Android, después de cambiar la configuración en,

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";

Para trasladar la aplicación a iOS, utilicé la demostración de la cámara iOS, https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/camera

y usó el mismo archivo gráfico y cambió la configuración en,

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";

Después de esto la aplicación está trabajando en iOS, sin embargo ...

La aplicación en Android funciona mucho mejor que iOS para detectar imágenes clasificadas. Si lleno el puerto de vista de la cámara con la imagen, ambos funcionan de forma similar. Pero normalmente la imagen a detectar es solo una parte del puerto de visualización de la cámara, en Android esto no parece afectar mucho, pero en iOS tiene un gran impacto, por lo que iOS no puede clasificar la imagen.

Mi conjetura es que Android está recortando si el puerto de visualización de la cámara al área central de 299x299, mientras que iOS está ampliando su puerto de visualización de la cámara al área central de 299x299.

¿Alguien puede confirmar esto? ¿Alguien sabe cómo arreglar la demostración de iOS para detectar mejor las imágenes enfocadas? (hacer que la cosecha)

En la clase de demostración de 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);

y en iOS se tiene,

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

Creo que el problema está aquí.

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

Tengo entendido que esto es solo escalar al tamaño 299 seleccionando cada píxel en lugar de escalar la imagen original al tamaño 299. Por lo tanto, esto conduce a una mala escala y un pobre reconocimiento de la imagen.

La solución es primero escalar a pixelBuffer a tamaño 299. Intenté esto,

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];

y para convertir la imagen a pixle buffer,

- (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; }

No estoy seguro de si esta es la mejor manera de cambiar el tamaño, pero esto funcionó. Pero parecía empeorar la clasificación de la imagen, no mejor ...

¿Alguna idea, o problemas con la conversión de imagen / cambio de tamaño?


La detección de objetos de Tensorflow tiene configuraciones predeterminadas y estándar, a continuación se muestra la lista de ajustes,

Cosas importantes que necesita verificar en función de su modelo de entrada ML

-> model_file_name: esto de acuerdo con su nombre de archivo .pb,

-> model_uses_memory_mapping: depende de usted para reducir el uso general de la memoria.

-> labels_file_name - Esto varía en función de nuestro nombre de archivo de etiqueta,

-> input_layer_name / output_layer_name: asegúrese de que está usando sus propios nombres de entrada / salida de capa que está utilizando durante la creación del archivo de gráfico (.pb).

fragmento de código

// 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";

Detección de flujo de tensión de imagen personalizada, puede usar el siguiente fragmento de trabajo:

-> Para este proceso solo necesitas pasar el objeto 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<tensorflow::Session> 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<std::string> 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<tensorflow::uint8> image_data = LoadImageFromFile( // [image_path UTF8String], &image_width, &image_height, &image_channels); std::vector<tensorflow::uint8> 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<float, 4>(); 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<tensorflow::Tensor> 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<std::pair<float, int> > top_results; GetTopN(output->flat<float>(), 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; }

Imagen de escala para ancho y altura personalizados - fragmento de código C ++,

std::vector<uint8> 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<uint8> 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 función anterior le ayuda a cargar los datos de la imagen según su proporción personalizada. La relación de píxeles de imagen de alta precisión para el ancho y la altura durante la clasificación de tensorflow es de 224 x 224.

Debe llamar a la función LoadImage por encima de RunInferenceOnImageResult, con los argumentos reales de ancho y altura personalizados junto con la referencia de imagen.


Por favor cambie en este código:

// 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";

Aquí cambio: const float input_std = 1.0f;