图像识别

我们的大脑使视觉看起来很容易。人类不会分解一只狮子和一只美洲虎,看一个标志,或认出一个人的脸。但这些实际上是用计算机解决的难题:他们看起来很容易,因为我们的大脑非常好地理解图像。

近几年机器学习领域在解决这些困难问题上取得了巨大进步。特别地,我们发现一种称为深卷积神经网络的模型 可以在硬性视觉识别任务上实现合理的性能 - 匹配或超过某些领域的人类表现。

通过验证其对ImageNet的工作,研究人员已经证明了计算机视觉的稳步进展,这是计算机视觉 的学术基准。连续模式继续显示改进,每次实现新的最先进的结果: QuocNetAlexNetInception(GoogLeNet)BN-Inception-v2。Google内部和外部的研究人员发表了描述所有这些模型的论文,但结果仍难以重现。我们现在正在采取下一步,发布在最新型号Inception-v3上运行图像识别的代码。

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

为了比较模型,我们检查了模型无法预测正确答案作为他们前5个猜测之一的频率 - 称为“前5个错误率”。 AlexNet通过在2012年验证数据集上设置了15%的前5个错误率来实现; 初创(GoogLeNet)达到6.67%; BN-Inception-v2达到4.9%; 初创v3达到3.46%。

人类对ImageNet挑战有多好?Andrej Karpathy 发表一篇博客,他试图衡量自己的表现。他达到了5.1%的前五名错误率。

本教程将教你如何使用Inception-v3。您将学习如何使用Python或C ++ 将图像分类为1000个类。我们还将讨论如何从此模型中提取更高级别的功能,这些功能可能被重用于其他视觉任务。

我们很高兴看到社区将如何处理这种模式。

 

使用Python API

classify_image.pytensorflow.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
在这种情况下,我们正在使用海军上将魔法师的默认图像 ,您可以看到网络正确识别她穿着军装,得分高达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_widthinput_height标志。我们还需要将从0到255之间的整数的像素值缩放到图形运算的浮点值。我们使用input_meaninput_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));
然后,我们开始创建我们要运行的小型模型的节点,以加载,调整大小和缩放像素值,以获得主模型期望作为其输入的结果。我们创建的第一个节点只是一个Constop,它包含我们要加载的图像的文件名的张量。那就是作为第一个输入ReadFile。您可能会注意到我们b.opts()作为最后一个参数传递给所有op创建函数。该参数确保将节点添加到模型定义中GraphDefBuilder。我们也打电话给ReadFile 运营商。这给了一个节点的名字,这并不是绝对必要的,因为如果你不这样做,就会分配一个自动的名字,但这样会使调试变得更容易一些。WithName() b.opts() 
  // 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 对象,它是实际运行图形的接口,并运行它,指定要获取输出的节点以及输出数据的位置。

这给我们一个Tensor对象的向量,在这种情况下,我们知道只有一个对象很长。Tensor在这种情况下,您可以将其视为多维数组,并将其作为浮点值保存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, "'");
  }
如果您浏览了图像加载代码,则很多术语应该看起来很熟悉。而不是使用a GraphDefBuilder来生成一个GraphDef对象,我们加载一个直接包含的protobuf文件GraphDef
  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();
}
然后我们创建一个Session对象,GraphDef并将其传递给调用者,以便以后可以运行它。

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启动器教程轻轻点 一下。最后,如果你想加快在这方面的研究速度,你可以阅读本教程中引用的所有论文的最新作品。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值