我需要找到一种方法来使用OPENCV for android来聚类二进制向量。
我正在使用一个单词模型,直到现在我正在使用java和使用sift在我的PC上工作。 问题是筛选不在OPENCV for android上,所以我决定尝试使用ORB,但它是一个二进制描述符。 所以我需要找到一种在android上使用opencv来聚类二进制向量的方法。
答案 0 :(得分:0)
首先,您可以尝试使用JFeatureLib,javasift(此处可以找到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;
}