根据人脸预测年龄性别和情绪代码实现 (c++ + caffe)(四)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hpyMiss/article/details/80867733

目录

人脸面部情绪识别 (一)

人脸面部情绪识别(二)

人脸面部情绪识别 age&gender(三)

根据人脸预测年龄性别和情绪代码实现 (c++ + caffe)(四)


一、 准备工作


1、caffe在windows下的环境搭建

环境搭建每个人由于系统环境不同而遇到的问题也不同,不要着急慢慢解决,我用的vs2015,所以只能自己编译caffe,最终出来一个build文件夹,里面有Caffe.sln,打开它把里面ALL_BUILD生成待用。

2、模板下载

解压后如下图:
这里写图片描述

3、代码详解

  • examples/cpp_classification/classification.cpp

    这是官方源码给的一个分类的例子,caffe中给出了分类的实例源代码,在初学时会调用生成的classification.exe对mnist手写字符图像进行分类。首先,用注释的方式对源码进行详细的说明。另外,这个例子用了类的概念且内容比较繁杂,需要改写成在实际测试中使用的方式。具体请参看源码详解
    caffe中用自带的classification.exe对单张图片进行分类识别时,一定要用到均值文件,写bat文件时也要写均值文件的路径。由于目标识别领域中用caffe时一般都有这个减均值的过程,将减均值过后的图片输入到第一个卷基层里,可以提高识别率。但是有些特殊领域不需要减均值这一步骤,比如图像取证中对某些特殊篡改后的图片进行训练时,输入到第一个卷基层的特征不是减均值过后的,比如是减去中值滤波过后的特征图片(MFR),因此这些领域用这个caffe自带的classification.exe时,就需要去掉均值这一部分。具体请参看详解

温馨提示:到此为止,生成classification.exe之后,输入参数就可以出来结果了,想多学继续往下玩,不想多动手的也可以去给的下载链接下载

convert_imageset.cpp (Caffe中的图像转换工具)

// This program converts a set of images to a lmdb/leveldb by storing them
// as Datum proto buffers.
// Usage:
//   convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME
//
// where ROOTFOLDER is the root folder that holds all the images, and LISTFILE
// should be a list of files as well as their labels, in the format as
//   subfolder1/file1.JPEG 7
//   ....

#include <algorithm>
#include <fstream>  // NOLINT(readability/streams)
#include <string>
#include <utility>
#include <vector>

#include "boost/scoped_ptr.hpp"
#include "gflags/gflags.h"
#include "glog/logging.h"

#include "caffe/proto/caffe.pb.h"
#include "caffe/util/db.hpp"
#include "caffe/util/format.hpp"
#include "caffe/util/io.hpp"
#include "caffe/util/rng.hpp"

using namespace caffe;  // NOLINT(build/namespaces)
using std::pair;
using boost::scoped_ptr;

DEFINE_bool(gray, false,
    "When this option is on, treat images as grayscale ones");
DEFINE_bool(shuffle, false,
    "Randomly shuffle the order of images and their labels");
DEFINE_string(backend, "lmdb",
        "The backend {lmdb, leveldb} for storing the result");
DEFINE_int32(resize_width, 0, "Width images are resized to");
DEFINE_int32(resize_height, 0, "Height images are resized to");
DEFINE_bool(check_size, false,
    "When this option is on, check that all the datum have the same size");
DEFINE_bool(encoded, false,
    "When this option is on, the encoded image will be save in datum");
DEFINE_string(encode_type, "",
    "Optional: What type should we encode the image as ('png','jpg',...).");

int main(int argc, char** argv) {
#ifdef USE_OPENCV
  ::google::InitGoogleLogging(argv[0]);
  // Print output to stderr (while still logging)
  FLAGS_alsologtostderr = 1;

#ifndef GFLAGS_GFLAGS_H_
  namespace gflags = google;
#endif

  gflags::SetUsageMessage("Convert a set of images to the leveldb/lmdb\n"
        "format used as input for Caffe.\n"
        "Usage:\n"
        "    convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME\n"
        "The ImageNet dataset for the training demo is at\n"
        "    http://www.image-net.org/download-images\n");
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  if (argc < 4) {
    gflags::ShowUsageWithFlagsRestrict(argv[0], "tools/convert_imageset");
    return 1;
  }

  const bool is_color = !FLAGS_gray;
  const bool check_size = FLAGS_check_size;
  const bool encoded = FLAGS_encoded;
  const string encode_type = FLAGS_encode_type;

  std::ifstream infile(argv[2]);
  std::vector<std::pair<std::string, int> > lines;
  std::string line;
  size_t pos;
  int label;
  while (std::getline(infile, line)) {
    pos = line.find_last_of(' ');
    label = atoi(line.substr(pos + 1).c_str());
    lines.push_back(std::make_pair(line.substr(0, pos), label));
  }
  if (FLAGS_shuffle) {
    // randomly shuffle data
    LOG(INFO) << "Shuffling data";
    shuffle(lines.begin(), lines.end());
  }
  LOG(INFO) << "A total of " << lines.size() << " images.";

  if (encode_type.size() && !encoded)
    LOG(INFO) << "encode_type specified, assuming encoded=true.";

  int resize_height = std::max<int>(0, FLAGS_resize_height);
  int resize_width = std::max<int>(0, FLAGS_resize_width);

  // Create new DB
  scoped_ptr<db::DB> db(db::GetDB(FLAGS_backend));
  db->Open(argv[3], db::NEW);
  scoped_ptr<db::Transaction> txn(db->NewTransaction());

  // Storing to db
  std::string root_folder(argv[1]);
  Datum datum;
  int count = 0;
  int data_size = 0;
  bool data_size_initialized = false;

  for (int line_id = 0; line_id < lines.size(); ++line_id) {
    bool status;
    std::string enc = encode_type;
    if (encoded && !enc.size()) {
      // Guess the encoding type from the file name
      string fn = lines[line_id].first;
      size_t p = fn.rfind('.');
      if ( p == fn.npos )
        LOG(WARNING) << "Failed to guess the encoding of '" << fn << "'";
      enc = fn.substr(p);
      std::transform(enc.begin(), enc.end(), enc.begin(), ::tolower);
    }
    status = ReadImageToDatum(root_folder + lines[line_id].first,
        lines[line_id].second, resize_height, resize_width, is_color,
        enc, &datum);
    if (status == false) continue;
    if (check_size) {
      if (!data_size_initialized) {
        data_size = datum.channels() * datum.height() * datum.width();
        data_size_initialized = true;
      } else {
        const std::string& data = datum.data();
        CHECK_EQ(data.size(), data_size) << "Incorrect data field size "
            << data.size();
      }
    }
    // sequential
    string key_str = caffe::format_int(line_id, 8) + "_" + lines[line_id].first;

    // Put in db
    string out;
    CHECK(datum.SerializeToString(&out));
    txn->Put(key_str, out);

    if (++count % 1000 == 0) {
      // Commit db
      txn->Commit();
      txn.reset(db->NewTransaction());
      LOG(INFO) << "Processed " << count << " files.";
    }
  }
  // write the last batch
  if (count % 1000 != 0) {
    txn->Commit();
    LOG(INFO) << "Processed " << count << " files.";
  }
#else
  LOG(FATAL) << "This tool requires OpenCV; compile with USE_OPENCV.";
#endif  // USE_OPENCV
  return 0;
}

应用方法:
该工具通过命令行方式使用,命令行的格式如下:
convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME
其中DB_NAME后面还可以跟一些可选的参数设置,具体有哪些可选的参数参见“可选参数设置部分”
其中ROOTFOLDER为图像集的根目录
LISTFILE 为一个文件的路径,该文件中记录了图像集中的各图样的路径和相应的标注
DB_NAME为要生成的数据库的名字

  举个例子:
  convert_imageset ImgSetRootDir/ ImgFileList.txt imgSet.lmdb
  其中ImgFileList.txt(也即LISTFILE)的没一行给出一个图像的信息,如:subfolder1/file1.JPEG 7
  其中subfolder1/file1.JPEG为图像路径,7为该图像的类别,并且中间空一个空格

可选参数设置
gray:bool类型,默认为false,如果设置为true,则代表将图像当做灰度图像来处理,否则当做彩色图像来处理
shuffle:bool类型,默认为false,如果设置为true,则代表将图像集中的图像的顺序随机打乱
backend:string类型,可取的值的集合为{“lmdb”, “leveldb”},默认为”lmdb”,代表采用何种形式来存储转换后的数据
resize_width:int32的类型,默认值为0,如果为非0值,则代表图像的宽度将被resize成resize_width
resize_height:int32的类型,默认值为0,如果为非0值,则代表图像的高度将被resize成resize_height
check_size:bool类型,默认值为false,如果该值为true,则在处理数据的时候将检查每一条数据的大小是否相同
encoded:bool类型,默认值为false,如果为true,代表将存储编码后的图像,具体采用的编码方式由参数encode_type指定
encode_type:string类型,默认值为”“,用于指定用何种编码方式存储编码后的图像,取值为编码方式的后缀(如’png’,’jpg’,…)

带参数的命令:
convert_imageset ImgSetRootDir/ ImgFileList.txt imgSet.lmdb --gray=true --resize_width=160 --resize_height=160

二、caffe网络结构以及如何产生去均值文件

这里写图片描述

本文执行create_iamgenet.sh脚本的时候,不加–gray=true参数时,能够生成lmdb,产生的是3通道的,而表情识别的net输入数据是1通道的,所有得再生成一个1通道的去均值文件,方便最后使用。最后的解决办法是直接用上面生成的convert_imageset.exe实现。例如我的:convert_imageset E:\test\faceR\data\train\ E:\test\faceR\data\train.txt E:\test\faceR\test –gray=true。这样就能生成我需要的1通道。最终产生的mean.binnryproto文件就是我需要的一通道去均值文件。

注意:三通道对应的均值(104,117,123)。一通道对应的均值(129.74)。是根据fer2013数据集计算所得。所以也可以改代码不需要mean.binnryproto文件,最后直接减去均值也可以,至于怎么改代码,上面提到了。



效果截图

输入的图片是人脸图,没有检测人脸的步骤
这里写图片描述
这里写图片描述这里写图片描述这里写图片描述

本文最终修改的代码下载

猜你喜欢

转载自blog.csdn.net/hpyMiss/article/details/80867733
今日推荐