xiangze's sparse blog

機械学習、ベイズ統計、コンピュータビジョンと関連する数学について

固有AKB

レコード大賞には間にあいませんが、紅白のまえにごり押しました。
こちらの画像を使いました。
http://akb48matomemo.com/wp-content/uploads/a14738fa6ac358c1f3e90347f2e32e91.jpg

手順

まず単純に48等分します。
split.cpp

#include <iostream>
#include <string>
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
int
main(int argc, char *argv[]){
  Mat in=imread(argv[1]);
  IplImage* src= 0; 
  int w=in.cols;
  int h=in.rows;
  int x=8;
  int y=6;
  char p[32];
  int ch =in.channels();
  for(int j=0;j<h;j+=h/y){
    for(int i=0;i<w;i+=w/x){
      Mat roi_img(in, Rect(i,j,w/x,h/y));
      String outname(argv[2]);
      sprintf(p,"%d",i/(w/x)+j/(h/y)*x);
      outname=outname+String(p)+".bmp";
      imwrite(outname,roi_img);
    }
  }
  return 0;
}

2次元の画像の輝度成分のみをとりスケーリングし、1次元化します。
分散共分散行列の固有値を求め、上位48個を図示します。
(固有値、ベクトル自体は画像サイズ分だけ得られるので取り出す数は任意です。
顔認識に用いる際は速度と正確性の兼ね合いで決定されるらしいです。)

EigenAKB.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <eigen3/Eigen/Dense>
// #include <eigen3/Eigen/SVD>
// #include <eigen3/Eigen/LU>
using namespace cv;
bool outmonoimg=false;
int
main(int argc, char *argv[]){
  char p[32];
  int num=48;//!!
  int sizen=64;
  String nametr(argv[1]);
  char outname[100];
  if(argc>2)
    sizen=atoi(argv[2]);
  int sizex=sizen;
  int sizey=sizen;
  int totsize=sizex*sizey;
  Mat dst[num];
  Eigen::VectorXf ave(totsize);
  Eigen::MatrixXf covmat(totsize,totsize);
  for(int i=0;i<num;i++){
    sprintf(p,"%d",i);
    String inname=nametr+p+".bmp";
    Mat in=imread(inname,0);//mono
    resize(in,dst[i],cv::Size(),(double)sizex/in.cols,(double)sizey/in.rows);
    if(outmonoimg){
      std::sprintf(outname,"%s%d_mono.bmp",nametr.c_str(),i);
      imwrite(outname,dst[i]);
    }
  }
  for(int p=0;p<num;p++)
    for(int i=0;i<totsize;i++)
      ave(i)+=dst[p].data[i];
  ave/=num;
  for(int p=0;p<num;p++)
    for(int i=0;i<totsize;i++)
      for(int j=0;j<totsize;j++){
    covmat(i,j)+=dst[p].data[i]*dst[p].data[j];
      }
  covmat/=num;
  for(int i=0;i<totsize;i++)
    for(int j=0;j<totsize;j++){
      covmat(i,j)-=ave(i)*ave(j);
    }
  Mat eaout(sizex,sizey,CV_8UC1);
  for(int i=0;i<totsize;i++){
    eaout.data[i]=ave(i);
  }
  imwrite(nametr+"_ave.bmp",eaout);
  //PCA
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> eig(covmat);
  Eigen::MatrixXf evecs=eig.eigenvectors();
  for(int n=0;n<num;n++){
    Mat eout(sizex,sizey,CV_8UC1);
    for(int i=0;i<totsize;i++){
      float m=evecs.col(n).minCoeff();
      float M=evecs.col(n).maxCoeff();
      eout.data[i]=((evecs(i,n)-m)*255)/(M-m);
    }
#ifndef NDEBUG
    std::cout <<evecs.col(n) <<std::endl;
    std::cout <<evecs.col(n) <<std::endl;
    =abs(evecs(i,n)/(evecs.col(n).maxCoeff()-evecs.col(n).minCoeff()))*255;
#endif
    std::sprintf(outname,"%s%d_e%d.bmp",nametr.c_str(),n,sizen);
    imwrite(outname,eout);
  }
  return 0;
}

結果

イマイチです。
8*6=48

最大固有値

うっすらと人の輪郭が見えます。

反省点

ごり押しなので改善すべき点は多々あります。

  1. 画質面
    1. 画像の切り取り方、顔周辺だけ切り取るべき(端っこの方は切れてしまっています。)
    2. 顔の角度をそろえる(Appearance model?)
  2. 実装面
    1. 分散、共分散の計算にライブラリを用いる。
    2. Opencv2.3以降で使えるcv::MatとEigenの行列の相互変換を用いる。http://opencv.jp/cookbook/opencv_mat.html#cv-mateigen-matrix
    3. Opencvの特異値分解をなぜ用いないのか。
    4. REDSVD::RedPCAを使う(上とは排他的です)。

感想

OpenCV,Eigenなどを使いこなせばC++でもいろいろなことができることがわかりました。