Image Recognition

来源:互联网 发布:red hat linux介绍 编辑:程序博客网 时间:2024/04/29 04:05
图像识别
    我们的大脑让视觉看起来很容易。对于人类来说,分开一只狮子和一只美洲虎,读一个标牌,或者认出一个人的脸,并不费事。但这些实际上是用计算机解决的难题:它们看起来很容易,因为我们的大脑非常善于理解图像。
    在过去的几年中,机器学习领域在解决这些难题方面取得了巨大的进步。具体而言,我们发现一种称为深度卷积神经网络的模型能够在硬视觉识别任务上达到合理的性能 - 在某些领域匹配或超出人类的表现。
    研究人员通过验证他们对ImageNet的工作证明了计算机视觉方面的稳定进展,这是计算机视觉的学术基准。连续的模型继续显示出改进,每一次都实现了一个新的最新的结果:QuocNet,AlexNet,Inception(GoogLeNet),BN-Inception-v2。 Google内部和外部的研究人员已经发表了描述所有这些模型的论文,但结果仍然难以重现。我们正在通过发布我们的最新模型Inception-v3上运行图像识别的代码来进行下一步。

    Inception-v3使用2012年的数据对ImageNet大型视觉识别挑战进行了培训。这是计算机视觉中的一项标准任务,模型试图将整个图像分为1000个类,如“Zebra”,“Dalmatian”和“Dishwasher ”。例如,下面是AlexNet对一些图像进行分类的结果:


    为了比较模型,我们检查了模型未能预测正确答案的频率,作为他们前5个猜测之一 - 被称为“前5位错误率”。 AlexNet通过设置2012年验证数据集的前5位错误率达到15.3%初始(GoogLeNet)达到6.67%;BN-Inception-v2达到4.9%;Inception-v3达到3.46%。
    人类在ImageNet挑战中的表现如何? Andrej Karpathy有一篇博客文章试图衡量自己的表现。他达到了5.1%的五大错误率。
    本教程将教你如何使用Inception-v3。您将学习如何将图像分类到Python或C ++的1000个类中。我们还将讨论如何从这个模型中提取更高级别的特征,这些特征可能会被其他视觉任务重用。
    我们很高兴看到社区将如何使用这个模型。
一、与Python API一起使用
    当程序第一次运行时,classify_image.py从tensorflow.org下载训练好的模型。您的硬盘上需要大约200M的可用空间。
    首先克隆GitHub的TensorFlow模型回购。运行以下命令:
cd models/tutorials/image/imagenet
python classify_image.py

    上述命令将分类提供的一只熊猫的图像。


    如果模型正确运行,脚本将产生以下输出:
giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.88493)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00878)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00317)
custard apple (score = 0.00149)
earthstar (score = 0.00127)

    如果你想提供其他JPEG图像,你可以通过编辑--image_file参数来实现。
    如果将模型数据下载到其他目录,则需要将--model_dir指向所使用的目录。
二、与C ++ API一起使用
    您可以在C ++中运行相同的Inception-v3模型,以用于生产环境。 您可以下载包含定义模型的GraphDef的存档(从TensorFlow存储库的根目录运行):
curl -L "https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz" |
  tar -C tensorflow/examples/label_image/data -xz

    接下来,我们需要编译C ++二进制文件,其中包含加载和运行图形的代码。 如果您已按照说明下载适用于您的平台的TensorFlow源代码安装,则应该可以通过在shell终端运行以下命令来构建该示例:
bazel build tensorflow/examples/label_image/...
    这应该创建一个二进制可执行文件,然后可以像这样运行:
bazel-bin/tensorflow/examples/label_image/label_image
    这使用框架附带的默认示例图像,并应输出类似于此的内容:
I tensorflow/examples/label_image/main.cc:206] military uniform (653): 0.834306
I tensorflow/examples/label_image/main.cc:206] mortarboard (668): 0.0218692
I tensorflow/examples/label_image/main.cc:206] academic gown (401): 0.0103579
I tensorflow/examples/label_image/main.cc:206] pickelhaube (716): 0.00800814
I tensorflow/examples/label_image/main.cc:206] bulletproof vest (466): 0.00535088

    在这种情况下,我们使用Grace Hopper的默认图像,你可以看到网络正确地识别出她穿着军装,得分高达0.8。


    接下来,通过提供--image =参数(例如,
bazel-bin/tensorflow/examples/label_image/label_image --image=my_image.png
    如果您在tensorflow / examples / label_image / main.cc文件中查看,您可以了解它是如何工作的。我们希望这段代码能帮助您将    TensorFlow集成到您自己的应用程序中,所以我们将一步一步地完成主要功能:
    命令行标志控制文件的加载位置,以及输入图像的属性。该模型预计将获得广场299x299 RGB图像,所以这些是input_width和    input_height标志。我们还需要将像素值从0到255之间的整数缩放到图形操作的浮点值。我们使用input_mean和input_std标志来控制缩放:我们首先从每个像素值中减去input_mean,然后用input_std对其进行分割。
    这些值可能看起来有些神奇,但是它们只是由原始模型作者根据他/她想要用作训练的输入图像来定义的。如果您有自己训练过的图表,则只需调整这些值以符合您在训练过程中使用的任何值即可。
    您可以看到它们是如何应用到ReadTensorFromImageFile()函数中的图像的。
// Given an image file name, read in the data, try to decode it as an image,
// resize it to the requested size, and then scale the values as desired.
Status ReadTensorFromImageFile(string file_name, const int input_height,
                               const int input_width, const float input_mean,
                               const float input_std,
                               std::vector<Tensor>* out_tensors) {
  tensorflow::GraphDefBuilder b;
    我们首先创建一个GraphDefBuilder,它是一个我们可以用来指定要运行或加载的模型的对象。
  string input_name = "file_reader";
  string output_name = "normalized";
  tensorflow::Node* file_reader =
      tensorflow::ops::ReadFile(tensorflow::ops::Const(file_name, b.opts()),
                                b.opts().WithName(input_name));

    然后,我们开始为要运行的小模型创建节点,以加载,调整大小和缩放像素值,以获得主模型期望的结果作为其输入。 我们创建的第一个节点只是一个Const运算符,它与我们想要加载的图像的文件名保持一个张量。 然后作为第一个输入传递给ReadFile操作。 您可能会注意到我们将b.opts()作为所有op创建函数的最后一个参数。 该参数确保将该节点添加到GraphDefBuilder中保存的模型定义中。 我们还通过对b.opts()进行WithName()调用来命名ReadFile运算符。 这给了节点一个名字,这不是必须的,因为如果你不这样做,会自动分配一个名字,但它确实使得调试更容易一些。
  // Now try to figure out what kind of file it is and decode it.
  const int wanted_channels = 3;
  tensorflow::Node* image_reader;
  if (tensorflow::StringPiece(file_name).ends_with(".png")) {
    image_reader = tensorflow::ops::DecodePng(
        file_reader,
        b.opts().WithAttr("channels", wanted_channels).WithName("png_reader"));
  } else {
    // Assume if it's not a PNG then it must be a JPEG.
    image_reader = tensorflow::ops::DecodeJpeg(
        file_reader,
        b.opts().WithAttr("channels", wanted_channels).WithName("jpeg_reader"));
  }
  // Now cast the image data to float so we can do normal math on it.
  tensorflow::Node* float_caster = tensorflow::ops::Cast(
      image_reader, tensorflow::DT_FLOAT, b.opts().WithName("float_caster"));
  // The convention for image ops in TensorFlow is that all images are expected
  // to be in batches, so that they're four-dimensional arrays with indices of
  // [batch, height, width, channel]. Because we only have a single image, we
  // have to add a batch dimension of 1 to the start with ExpandDims().
  tensorflow::Node* dims_expander = tensorflow::ops::ExpandDims(
      float_caster, tensorflow::ops::Const(0, b.opts()), b.opts());
  // Bilinearly resize the image to fit the required dimensions.
  tensorflow::Node* resized = tensorflow::ops::ResizeBilinear(
      dims_expander, tensorflow::ops::Const({input_height, input_width},
                                            b.opts().WithName("size")),
      b.opts());
  // Subtract the mean and divide by the scale.
  tensorflow::ops::Div(
      tensorflow::ops::Sub(
          resized, tensorflow::ops::Const({input_mean}, b.opts()), b.opts()),
      tensorflow::ops::Const({input_std}, b.opts()),
      b.opts().WithName(output_name));
    然后,我们继续添加更多的节点,将文件数据解码为图像,将整数转换为浮点值,调整大小,最后对像素值进行减法和除法运算。
  // This runs the GraphDef network definition that we've just constructed, and
  // returns the results in the output tensor.
  tensorflow::GraphDef graph;
  TF_RETURN_IF_ERROR(b.ToGraphDef(&graph));
    在这个结尾,我们有一个存储在b变量中的模型定义,我们用ToGraphDef()函数把它转换成完整的图形定义。
  std::unique_ptr<tensorflow::Session> session(
      tensorflow::NewSession(tensorflow::SessionOptions()));
  TF_RETURN_IF_ERROR(session->Create(graph));
  TF_RETURN_IF_ERROR(session->Run({}, {output_name}, {}, out_tensors));
  return Status::OK();

    然后我们创建一个tf.Session对象,它是实际运行图形的接口,并运行它,指定我们想要从哪个节点获取输出,以及将输出数据放在哪里。
    这给了我们一个张量对象的向量,在这种情况下,我们知道只有一个单独的对象。 在这种情况下,您可以将张量视为一个多维数组,并将它保存为299像素高,299像素宽,3通道图像作为浮点值。 如果您的产品中已经有了自己的图像处理框架,只要在将图像输入主图之前应用相同的转换,就可以使用该框架。
    这是一个在C ++中动态创建小型TensorFlow图形的简单示例,但对于预先训练的Inception模型,我们希望从文件中加载更大的定义。 你可以看到我们如何在LoadGraph()函数中做到这一点。
// Reads a model graph definition from disk, and creates a session object you
// can use to run it.
Status LoadGraph(string graph_file_name,
                 std::unique_ptr<tensorflow::Session>* session) {
  tensorflow::GraphDef graph_def;
  Status load_graph_status =
      ReadBinaryProto(tensorflow::Env::Default(), graph_file_name, &graph_def);
  if (!load_graph_status.ok()) {
    return tensorflow::errors::NotFound("Failed to load compute graph at '",
                                        graph_file_name, "'");
  }

    如果你已经看过图像加载代码,很多的术语应该看起来很熟悉。 我们不使用GraphDefBuilder生成GraphDef对象,而是加载一个直接包含GraphDef的protobuf文件。
  session->reset(tensorflow::NewSession(tensorflow::SessionOptions()));
  Status session_create_status = (*session)->Create(graph_def);
  if (!session_create_status.ok()) {
    return session_create_status;
  }
  return Status::OK();
}

    然后,我们从GraphDef创建一个Session对象,并将其传回给调用者,以便稍后再运行它。
    GetTopLabels()函数与图像加载非常相似,除了在这种情况下,我们想要获得运行主图的结果,并将其转换为最高得分标签的排序列表。 就像图像加载器一样,它创建一个GraphDefBuilder,添加一些节点,然后运行短图形以获得一对输出张量。 在这种情况下,它们表示最高结果的排序分数和索引位置。
// Analyzes the output of the Inception graph to retrieve the highest scores and
// their positions in the tensor, which correspond to categories.
Status GetTopLabels(const std::vector<Tensor>& outputs, int how_many_labels,
                    Tensor* indices, Tensor* scores) {
  tensorflow::GraphDefBuilder b;
  string output_name = "top_k";
  tensorflow::ops::TopK(tensorflow::ops::Const(outputs[0], b.opts()),
                        how_many_labels, b.opts().WithName(output_name));
  // This runs the GraphDef network definition that we've just constructed, and
  // returns the results in the output tensors.
  tensorflow::GraphDef graph;
  TF_RETURN_IF_ERROR(b.ToGraphDef(&graph));
  std::unique_ptr<tensorflow::Session> session(
      tensorflow::NewSession(tensorflow::SessionOptions()));
  TF_RETURN_IF_ERROR(session->Create(graph));
  // The TopK node returns two outputs, the scores and their original indices,
  // so we have to append :0 and :1 to specify them both.
  std::vector<Tensor> out_tensors;
  TF_RETURN_IF_ERROR(session->Run({}, {output_name + ":0", output_name + ":1"},
                                  {}, &out_tensors));
  *scores = out_tensors[0];
  *indices = out_tensors[1];
  return Status::OK();

    PrintTopLabels()函数获取这些排序结果,并以友好的方式打印出来。 CheckTopLabel()函数非常相似,但只是确保顶层标签是我们期望的,用于调试目的。
    最后,main()将所有这些调用关联在一起。
int main(int argc, char* argv[]) {
  // We need to call this to set up global state for TensorFlow.
  tensorflow::port::InitMain(argv[0], &argc, &argv);
  Status s = tensorflow::ParseCommandLineFlags(&argc, argv);
  if (!s.ok()) {
    LOG(ERROR) << "Error parsing command line flags: " << s.ToString();
    return -1;
  }

  // First we load and initialize the model.
  std::unique_ptr<tensorflow::Session> session;
  string graph_path = tensorflow::io::JoinPath(FLAGS_root_dir, FLAGS_graph);
  Status load_graph_status = LoadGraph(graph_path, &session);
  if (!load_graph_status.ok()) {
    LOG(ERROR) << load_graph_status;
    return -1;
  }

    我们加载主图。
  // Get the image from disk as a float array of numbers, resized and normalized
  // to the specifications the main graph expects.
  std::vector<Tensor> resized_tensors;
  string image_path = tensorflow::io::JoinPath(FLAGS_root_dir, FLAGS_image);
  Status read_tensor_status = ReadTensorFromImageFile(
      image_path, FLAGS_input_height, FLAGS_input_width, FLAGS_input_mean,
      FLAGS_input_std, &resized_tensors);
  if (!read_tensor_status.ok()) {
    LOG(ERROR) << read_tensor_status;
    return -1;
  }
  const Tensor& resized_tensor = resized_tensors[0];

    加载,调整大小和处理输入图像。
  // Actually run the image through the model.
  std::vector<Tensor> outputs;
  Status run_status = session->Run({ {FLAGS_input_layer, resized_tensor}},
                                   {FLAGS_output_layer}, {}, &outputs);
  if (!run_status.ok()) {
    LOG(ERROR) << "Running model failed: " << run_status;
    return -1;
  }

    在这里,我们运行加载的图形作为输入图像。
  // This is for automated testing to make sure we get the expected result with
  // the default settings. We know that label 866 (military uniform) should be
  // the top label for the Admiral Hopper image.
  if (FLAGS_self_test) {
    bool expected_matches;
    Status check_status = CheckTopLabel(outputs, 866, &expected_matches);
    if (!check_status.ok()) {
      LOG(ERROR) << "Running check failed: " << check_status;
      return -1;
    }
    if (!expected_matches) {
      LOG(ERROR) << "Self-test failed!";
      return -1;
    }
  }

    出于测试目的,我们可以检查以确保我们得到我们期望的输出。
  // Do something interesting with the results we've generated.
  Status print_status = PrintTopLabels(outputs, FLAGS_labels);

    最后我们打印我们找到的标签。
  if (!print_status.ok()) {
    LOG(ERROR) << "Running print failed: " << print_status;
    return -1;
  }

    这里的错误处理是使用TensorFlow的Status对象,这非常方便,因为它可以让你知道ok()检查器是否发生了任何错误,然后可以打印出来给出一个可读的错误信息。
    在这种情况下,我们正在演示对象识别,但是您应该能够在您自己发现或训练的其他模型上使用非常类似的代码,跨越各种领域。我们希望这个小例子给你一些关于如何在你自己的产品中使用TensorFlow的想法。
    练习:转移学习是一个想法,如果你知道如何很好地解决任务,你应该能够转移一些理解来解决相关的问题。一种执行传递学习的方法是去除网络的最后分类层并提取CNN的倒数第二层,在这种情况下是2048维向量。在how-to部分有一个指导。
三、学习更多的资源
    为了了解一般的神经网络,Michael Nielsen的免费在线书籍是一个很好的资源。对于卷积神经网络来说,克里斯·奥拉(Chris Olah)有一些很好的博客文章,而迈克尔·尼尔森(Michael Nielsen)的这本书有一个非常棒的章节。

    要了解有关实现卷积神经网络的更多信息,可以跳到TensorFlow深度卷积网络教程,或者从我们的ML初学者或ML专家的MNIST入门教程开始更加轻松。最后,如果你想加快在这个领域的研究速度,你可以阅读本教程中引用的所有论文的最新工作。


本文主要使用谷歌翻译翻译。