Color Coherence Vector の実装

【追記:2013/03/25】αとβの求め方が論文と一致していないというご指摘を受け、該当箇所(//alphaと//betaの行)を修正いたしました。ご迷惑おかけしました。
Color Coherence Vector (CCV) というのは色に基づく画像検索の一手法です。結構昔の文献ながら最近の論文でもよく参照されてる有名どころの手法のようです。処理がシンプルなのでサクっと作りました。OpenCV2.1と【id:wosugi:20110628】で紹介したラベリング処理を使っています。

//ccv.hpp
#pragma once
#include <iostream>
#include <vector>
#include <cmath>
#include <C:/OpenCV2.1/include/opencv/cv.h>
#include "labeling.hpp" //前に公開したラベリング処理のヘッダ

using namespace std;
using namespace cv;

typedef vector<pair<int,int> > CCV;

const int NUM_CCV_COLOR=4*4*4;

const int W=240;
const int H=160;
Mat bufimg(H,W,CV_8UC3);

inline void setAt(Mat& img,int x,int y,uchar r,uchar g,uchar b)
{
    uchar* p=(img.data+y*img.step+3*x);
    p[0]=b;
    p[1]=g;
    p[2]=r;
}

//カラー画像からのCCV特徴の抽出
CCV extract(const Mat& image,int tau=25)
{
    resize(image,bufimg,Size(W,H));
    blur(bufimg,bufimg,Size(3,3)); //GaussianBlur(bufimg,bufimg,Size(3,3),0);

    const int MASK=0xC0;
    for(int j=0;j<H;j++)
    for(int i=0;i<W;i++)
    {
        uchar r,g,b;
        getAt(bufimg,i,j,r,g,b);
        setAt(bufimg,i,j,r&MASK,g&MASK,b&MASK);
    }

    static Mat_<int> label(H,W);
    int regions=doLabeling(bufimg,label);

    vector<int> count(regions,0);
    vector<uchar> r(regions,0);
    vector<uchar> g(regions,0);
    vector<uchar> b(regions,0);
    for(int j=0;j<H;j++)
    for(int i=0;i<W;i++)
    {
        int p=label(j,i);
        count[p]++;
        getAt(bufimg,i,j,r[p],g[p],b[p]);
    }

    CCV ccv(NUM_CCV_COLOR,pair<int,int>(0,0));
    for(int k=0;k<regions;k++)
    {
        int c=((r[k]>>6)<<4)+((g[k]>>6)<<2)+((b[k]>>6)<<0);
        if(tau<=count[k])
            ccv[c].first+=count[k];//alpha
        else
            ccv[c].second+=count[k];//beta
    }
    return ccv;
}

//CCV特徴ベクトル間の距離関数
double metric(const CCV& x,const CCV& y)
{
    double d=0.0;
    for(int k=0;k<NUM_CCV_COLOR;k++)
    {
        double a1=x[k].first;
        double b1=x[k].second;
        double a2=y[k].first;
        double b2=y[k].second;

        double da=fabs((a2-a1)/(a2+a1+1.0));
        double db=fabs((b2-b1)/(b2+b1+1.0));
        d+=da+db;
    }
    return d;
}

CCVは一枚のカラー画像から抽出される特徴ベクトルです。基本的には色の出現頻度を表すのですが、さらに色領域の面積で場合分けすることでより細かく特徴抽出しています。具体的には、まず画像サイズを適当に正規化後、色を64色(RGB空間を4x4x4ビンで表現)に量子化し同色な領域をまとめます。ここで色数分(つまり64)の要素を持つ配列αとβを用意します。各領域について面積が閾値τ以上であればα[c]+1、そうでなければβ[c]+1します【修正:2013/03/25】閾値τ以上であればα[c]に、そうでなければβ[c]に、その領域面積を加算します(論文では画素数をカウントしているので以前の実装は間違いです)。ただしcはその領域の色を示すインデックス(c∈{0,1,...,63})です。このαとβを合わせたものがCCVになります。
論文の記述との違いですが、正規化後のサイズは240x160=36800pixにしました(論文では38976pixとだけ書いてある)。τのデフォルト値は論文にある25にしてますが、画像サイズと相互依存なので注意が必要。また平滑化は、論文と同じ3x3の平均値フィルタを使ってますが、こちらの手元にあるデータでは3x3ガウスフィルタのほうが精度が高かったです(コメントアウトしてます)。
バグなどあったら教えてください。

参考文献