在OpenCV for Android中聚类二进制向量

时间:2014-05-01 10:47:46

标签: android opencv cluster-analysis

我需要找到一种方法来使用OPENCV for android来聚类二进制向量。

我正在使用一个单词模型,直到现在我正在使用java和使用sift在我的PC上工作。 问题是筛选不在OPENCV for android上,所以我决定尝试使用ORB,但它是一个二进制描述符。 所以我需要找到一种在android上使用opencv来聚类二进制向量的方法。

1 个答案:

答案 0 :(得分:0)

首先,您可以尝试使用JFeatureLibjavasift(此处可以找到javasift in android application)。

关于二进制群集,您可以查看 主教“模式识别和机器学习”参见第461-465页“伯努利分布的混合”一章。我有实现(见下面的代码),但所有评论都是俄语(我懒得翻译)。 This video显示了它如何适用于手写数字的二进制图像(二值化MNIST)。它找到了集群中心。

#include <iostream>
#include <vector>
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <numeric>
#include "fstream"
#include "iostream"
using namespace std;
using namespace cv;
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
inline void endian_swap(unsigned short& x)
{
    x = (x>>8) | 
        (x<<8);
}
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
inline void endian_swap(unsigned int& x)
{
    x = (x>>24) | 
        ((x<<8) & 0x00FF0000) |
        ((x>>8) & 0x0000FF00) |
        (x<<24);
}
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
inline void endian_swap(unsigned __int64& x)
{
    x = (x>>56) | 
        ((x<<40) & 0x00FF000000000000) |
        ((x<<24) & 0x0000FF0000000000) |
        ((x<<8)  & 0x000000FF00000000) |
        ((x>>8)  & 0x00000000FF000000) |
        ((x>>24) & 0x0000000000FF0000) |
        ((x>>40) & 0x000000000000FF00) |
        (x<<56);
}
//-----------------------------------------------------------------------------------------------------
// Чтение меток NMIST (здесь не используется)
//-----------------------------------------------------------------------------------------------------
void read_mnist_labels(string fname,vector<unsigned char>& vec_lbl)
{
    ifstream file;
    file.open(fname,ifstream::in | ifstream::binary);
    if (file.is_open())
    {
        unsigned  int magic_number=0;
        unsigned  int number_of_labels=0;
        file.read((char*)&magic_number,sizeof(magic_number)); //если перевернуть то будет 2051
        endian_swap(magic_number);
        file.read((char*)&number_of_labels,sizeof(number_of_labels));//если перевернуть будет 10к
        endian_swap(number_of_labels);

        cout << "magic_number=" << magic_number << endl;
        cout << "number_of_labels=" << number_of_labels << endl;

        for(int i=0;i<number_of_labels;++i)
        {
            unsigned char t_ch=0;
            file.read((char*)&t_ch,sizeof(t_ch));
            vec_lbl.push_back(t_ch);
        }
    }
}
//-----------------------------------------------------------------------------------------------------
// Чтение изображений MNIST, на выходе вектор матриц с изображениями цифр
//-----------------------------------------------------------------------------------------------------
void read_mnist(string fname,vector<Mat>& vec_img)
{
    ifstream file;
    file.open(fname,ifstream::in | ifstream::binary);
    if (file.is_open())
    {
        unsigned  int magic_number=0;
        unsigned  int number_of_images=0;
        unsigned  int n_rows=0;
        unsigned  int n_cols=0;
        file.read((char*)&magic_number,sizeof(magic_number)); //если перевернуть то будет 2051
        endian_swap(magic_number);
        file.read((char*)&number_of_images,sizeof(number_of_images));//если перевернуть будет 10к
        endian_swap(number_of_images);
        file.read((char*)&n_rows,sizeof(n_rows));
        endian_swap(n_rows);
        file.read((char*)&n_cols,sizeof(n_cols));
        endian_swap(n_cols);
        cout << "Магическое число=" << magic_number << endl;
        cout << "Высота изображений=" << n_rows << endl;
        cout << "Ширина изображений=" << n_cols << endl;
        cout << "Количество изображений=" << number_of_images << endl;

        for(int i=0;i<number_of_images;++i)
        {
            Mat temp(n_rows,n_cols,CV_8UC1);
            for(int r=0;r<n_rows;++r)
            {
                for(int c=0;c<n_cols;++c)
                {
                    unsigned char t_ch=0;
                    file.read((char*)&t_ch,sizeof(t_ch));
                    //тут идет запись матрицы 28х28 в вектор
                    temp.at<unsigned char>(r,c)= t_ch; //получаем бинаризованные изображения
                }
            }
            vec_img.push_back(temp);
        }
    }

}
//-----------------------------------------------------------------------------------------------------
// Считаем правдоподобие выборки данных x для распределения Бернулли с параметром mu
//-----------------------------------------------------------------------------------------------------
double Likelihood(Mat& x,Mat& mu)
{
    double P=1;
    int n_rows=x.rows;
    int n_cols=x.cols;
    for(int r=0;r<n_rows;++r)
    {
        for(int c=0;c<n_cols;++c)
        {
            // Распределение бинарное, поэтому можем позволить себе такое безобразие.
            if(x.at<double>(r,c)>0.5)
            {
                P*=mu.at<double>(r,c); // Если выпала единица
            }
            else
            {
                P*=1.0-mu.at<double>(r,c); // если выпал ноль
            }
        }
    }
    return P;
}
//-----------------------------------------------------------------------------------------------------
// E - шаг
// Вычисляем значения скрытых переменных.
// Постериорная вероятность принадлежности X к кластеру K
//-----------------------------------------------------------------------------------------------------
RNG rng;
void getGamma(vector<Mat>& vecX, vector<Mat>& vecMu,vector<double>& vecPi,Mat& Gamma)
{
    int N=vecX.size();
    int K=vecPi.size();
    for(int n=0;n<N;n++)
    {
        // Вычислим знаменатель (Маргинальная вероятность по классу)
        double denom=0;
        for(int j=0;j<K;j++)
        {
            denom+=Likelihood(vecX[n],vecMu[j]);
        }

        for(int k=0;k<K;k++)
        {
            double p=Likelihood(vecX[n],vecMu[k]);

            // Некоторая моя вычислительная алхимия
            double tmp;
            if(fabs(denom)>(2*DBL_MIN))
            {
                tmp=p/denom;
            }else
            {
                tmp=rng.gaussian(1/(K*sqrt(3.0)))+1.0/K; // пошумим чуть-чуть
            }
            // ------------------------------------
            Gamma.at<double>(n,k)=vecPi[k]*tmp;
            // Для каждого X сумма вероятностей принадлежности ко всем кластерам равна 1
            // (К какому нибудь кластеру он точно принадлежит.)
            normalize(Gamma.row(n),Gamma.row(n),1,0,cv::NORM_L1);
        }

    }
    // Проконтролируем на всякий случай границы (вероятность не может иметь значения вне диапазона [0;1])
    normalize(Gamma,Gamma,0,1,cv::NORM_MINMAX);
}
//-----------------------------------------------------------------------------------------------------
// M - шаг. Теперь по полному набору переменных (наблюдаемые и скрытые) находим следующее приближение параметров распределения.
//-----------------------------------------------------------------------------------------------------
void getMuAndPi(vector<Mat>& vecX,Mat& Gamma,vector<Mat>& vecMu,vector<double>& vecPi)
{
    int N=vecX.size();
    int K=vecPi.size();

    for(int k=0;k<K;k++)
    {
        double Nk=0;
        for(int n=0;n<N;n++)
        {
            Nk+=Gamma.at<double>(n,k);
        }

        // Эффективное количество элементов, принадлежащих K-тому кластеру
        cout << "N[" << k << "]=" << Nk << endl;  

        vecMu[k]=0;
        for(int n=0;n<N;n++)
        {
            vecMu[k]+=(vecX[n].mul(Gamma.at<double>(n,k)));
        }
        vecMu[k]/=Nk; // Находим приближение центров кластеров
        vecPi[k]=Nk/(double)N; // Находим приближение коэффициентов смеси
    }

    // Отмасштабируем коэффициенты смеси, чтобы сумма их была равна 1
    double sumPi=0;
    sumPi=std::accumulate(vecPi.begin(),vecPi.end(), 0.0);
    transform(vecPi.begin(), vecPi.end(), vecPi.begin(), std::bind2nd(std::divides<double>(),sumPi));
}
//-----------------------------------------------------------------------------------------------------
// Инициализация параметров
//-----------------------------------------------------------------------------------------------------
void InitParameters(int N,int K,vector<Mat>& MNIST,vector<Mat>& vecX,Mat& Gamma,vector<Mat>& vecMu,vector<double>& vecPi)
{
    Gamma=Mat::zeros(N,K,CV_64FC1);
    Mat tmp;
    int h=MNIST[0].cols;
    int w=MNIST[0].rows;

    vecX.resize(N);
    // Это делается чтобы можно было задать объем обрабатываемых данных (через N)
    for(int i=0;i<N;i++)
    {       
        threshold(MNIST[i],tmp,128,1,CV_8UC1);
        tmp.convertTo(vecX[i],CV_64FC1);
    }
    // инициализация центров кластеров
    for(int k=0;k<K;k++)
    {
        Mat r(MNIST[0].size(),CV_64FC1);
        randu(r,0,1);   
        normalize(r,r,MNIST[0].rows*MNIST[0].cols,0,cv::NORM_L1);
        r*=0.5;
        r+=0.25;
        // Центры кластеров (все пиксели имеют яркость в около 0.5)
        vecMu.push_back(r);
        // коэффициенты смеси инициализируются так, чтобы их сумма была равна единице
        vecPi.push_back(1.0/K);
    }
}
//-----------------------------------------------------------------------------------------------------
// Отрисовка центров кластеров в один ряд на одном изображении
//-----------------------------------------------------------------------------------------------------
void DrawMu(Mat& dst, vector<Mat>& vecMu)
{
    int rows=vecMu[0].rows;
    int cols=vecMu[0].cols;
    dst=Mat::zeros( rows, vecMu.size()*cols, CV_64FC1 );
    for(int i=0;i<vecMu.size();i++)
    {
        vecMu[i].copyTo(dst(Rect(i*cols,0,cols,rows)));
    }
    cv::normalize(dst,dst,0,1,cv::NORM_MINMAX);
}
//-----------------------------------------------------------------------------------------------------
// Bishop страницы 461-465 глава "Mixtures of Bernoulli distributions"
//-----------------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
    // Количество кластеров, которое хотим получить
    int N_clusters=10;
    // Количество итераций EM - алгоритма.
    int N_iter=50;

    setlocale(LC_ALL, "Russian");
    vector<Mat> MNIST;
    // читаем изображения
    cout << "Загрузка изображений." << endl;
    read_mnist("D:/MNIST/t10k-images.idx3-ubyte",MNIST);
    cout << "Загрузка изображений выполнена." << endl;
    // создаем окно
    namedWindow("result");
    // размеры одного изображения
    int rows=MNIST[0].rows;
    int cols=MNIST[0].cols;

    vector<Mat>     vecX;   // Входные векторы
    Mat             Gamma; // Мера ответственности K-того кластера за N-ный входной вектор
    vector<Mat>     vecMu;  // Центры кластеров
    vector<double>  vecPi;  // Коэффициенты смеси распределений

    // Создаем и заполняем необходимые переменные
    cout << "Инициализация параметров." << endl;
    InitParameters(10000,N_clusters,MNIST,vecX,Gamma,vecMu,vecPi);
    cout << "Инициализация параметров выполнена." << endl;

    // Запишем все в видеофайл
    VideoWriter vw=VideoWriter::VideoWriter("output.mpeg", CV_FOURCC('P','I','M','1'), 20, Size(cols*vecMu.size(),rows));
    // собственно сам EM-алгоритм
    // Критерием сходимости таких алгоритмов обычно является стабилизация значений центров кластеров
    // но мне лень это делать, поэтому поставлю фиксированное количество итераций.
    for(int iter=0;iter<N_iter;iter++)
    {
        cout << "Итерация №" << iter << endl;
        getGamma(vecX,vecMu,vecPi,Gamma);   //  E - Шаг (расчет матожиданий коэффициентов)
        cout << "E - шаг выполнен." << endl;
        getMuAndPi(vecX,Gamma,vecMu,vecPi); //  M - шаг (уточнение параметров распределения методом максимизации правдоподобия)
        cout << "M - шаг выполнен." << endl;

        // Отрисовка центров кластеров, чтобы не скучно было :)
        Mat MuImg;
        DrawMu(MuImg,vecMu);
        imshow("result",MuImg);
        MuImg.convertTo(MuImg,CV_8UC1,255);
        cvtColor(MuImg,MuImg,cv::COLOR_GRAY2BGR);
        //resize(MuImg,MuImg,Size(MuImg.cols*4,MuImg.rows*4));
        vw<<MuImg; // Запись кадра в видеофайл

        waitKey(30);
        MuImg.release();

    }
    // Освободим видеофайл
    vw.release(); 
    // Закончили, ждем нажатия клавиши
    waitKey(0);
    destroyAllWindows();
    return 0;
}