PCANet的C++代码——详细注释版


PCANet的C++代码:here。

代码注释:

cv::Mat im2colstep(cv::Mat& InImg, vector<int>& blockSize, vector<int>&stepSize)
{
	// block的行大小 * 列大小,定义OutBlock的列数。
	int r_row = blockSize[ROW_DIM] * blockSize[COL_DIM];
	// 图片的rows减去block的rows, 为了防止在滑动窗口的时候越界。 图片的发小要减去block的大小。
	int row_diff = InImg.rows - blockSize[ROW_DIM];
	int col_diff = InImg.cols - blockSize[COL_DIM];
	// 减完之后要除以步长需要再加1。计算所有block的个数,作为OutBlock的行数。
	int r_col = (row_diff / stepSize[ROW_DIM] + 1) * (col_diff / stepSize[COL_DIM] + 1);
	// 定义一个Mat 类型,用于保存提取的所有block。
	cv::Mat OutBlocks(r_col, r_row, InImg.depth());

	// 定义指向OutBlocks的行指针。
	double *p_InImg, *p_OutImg;
	// 计数。当前第几个block,或者说OutBlocks的第几行。
	int blocknum = 0;

	// block横向遍历图片。
	for(int j=0; j<=col_diff; j+=stepSize[COL_DIM])
	{
		// block纵向遍历图片。
		for(int i=0; i<row_diff; i+=stepSize[ROW_DIM])
		{
			// 初始化指针。
			p_OutImg = OutBlocks.ptr<double>(blocknum);

			// 下面就是将每个block的值保存到p_OutImg中。
			// 用来计数行数。
			for(int m=0; m<blockSize[ROW_DIM]; m++)
			{
				// 将InImg的第(i+m)行赋值给p_InImg。
				p_InImg = InImg.ptr<double>(i + m);
				// 用来计数列数。
				for(int l=0; l<blockSize[COL_DIM]; l++)
				{
					// 将p_InImg数组中的j+l个值赋给p_OutImg。也就是block的m行l列。
					// 注意在存放的时候是以列优先。
					p_OutImg[blockSize[ROW_DIM] * l + m] = p_InImg[j+l];
				}
			}
			blocknum++;
		}
	}
	return OutBlocks;
}

// 得到图片的所有channels的所有block。
cv::Mat im2col_general(cv::Mat& InImg, vector<int>& blockSize, vector<int>& stepSize)
{
	// block的大小,表示行和列。 & 遍历图片行列的步长。
	assert(blockSize.size() == 2 && stepSize.size() == 2);

	// 图片的通道数。
	int channels = InImg.channels();

	// 定义一个向量,保存InImg图片的每个通道。
	vector<cv::Mat> layers;
	// 如果InImg的通道数大于1,需要分割。
	if(channels > 1)
		split(InImg, layers);
	else
		// 通道数为1,不需要再分割。
		layers.push_back(InImg);

	// 得到InImg第一个通道图片的所有block。
	cv::Mat AllBlocks = im2colstep(layers[0], blockSize, stepSize);

	int size = layers.size();
	if(size > 1)
	{
		// 交换layers第一个元素和最后一个元素。目的:删除layers的第一个元素。
		swap(layers[0], layers.back());
		layers.pop_back();
		for(int i=1; i<size; i++)
		{
			// 实现图像的拼接。 将InImg的每个通道拼接到一起。
			hconcat(AllBlocks, im2colstep(layers[i], blockSize, stepSize));
		}
	}
	// 矩阵的转置。
	return AllBlocks.t();
}
// 求样本的特征向量。
cv::Mat PCA_FilterBank(vector<cv::Mat>& InImg, int PatchSize, int NumFileters)
{
	int channels = InImg[0].channels();
	int InImg_Size = InImg.size();

	int* randIdx = getRandom(InImg_Size);

    // size用来定义协方差矩阵的大小。
	int size = channels * PatchSize * PatchSize;
	int img_depth = InImg[0].depth();
	cv::Mat Rx = cv::Mat::zeros(size, size, img_depth);

	// 设置block的行数和列数。 设置横向遍历和纵向遍历的步长。
	vector<int> blockSize;
	vector<int> stepSize;
	for(int i=0; i<2; i++)
	{
		// 可见选取的block长宽相等,步长都为1。
		blockSize.push_back(PatchSize);
		stepSize.push_back(1);
	}

	// 用于保存得到的block。
	cv::Mat temp;
	// 存放样本的每个特征的均值。
	cv::Mat mean;
	// 存放样本每维减去对应的均值。
	cv::Mat temp2;
	cv::Mat temp3;

	// OpenMP指令。
	int coreNum = omp_get_num_procs();
	int cols = 0;
# pragma omp parallel for default(none) num_threads(coreNum) private(temp, temp2, temp3, mean) shared(cols, Rx, InImg_Size, InImg, randIdx, blockSize, stepSize)
	for(int j=0; j<InImg_Size; j++)
	{
		temp = im2col_general(InImg[randIdx[j]], blockSize, stepSize);

		// PCA第一步,求特征均值。在这里是以列为特征的,下边为什么要以行为特征?
		cv::reduce(temp, mean, 0, CV_REDUCE_AVG);
		temp3.create(0,temp.cols, temp.type());
		cols = temp.cols;

		// 所有样本减去均值。
		for(int i=0; i<temp.rows; i++)
		{
			temp2 = (temp.row(i) - mean.row(0));
			temp3.push_back(temp2.row(0));
		}
		
		temp = temp3 * temp3.t();
# pragma omp critical
		Rx = Rx + temp;
	}
	Rx = Rx / (double)(InImg_Size * cols);

	cv::Mat eValuesMat;
	cv::Mat eVectorsMat;

    // 求特征值和特征向量。
	eigen(Rx, eValuesMat,eVectorsMat);

	cv::Mat Filters(0, Rx.cols, Rx.depth());
    // 选取NumFileters个特征向量作为行向量组成特征向量矩阵。
	for(int i=0; i<NumFileters; i++)
		Filters.push_back(eVectorsMat.row(i));
	return Filters;
}
// 接下来,减去均值样本矩阵 * 特征向量就是PCA的输出。
PCA_Out_Result* PCA_output(vector<cv::Mat>& InImg, vector<int>& InImgIdx, int PatchSize, int NumFilters, cv::Mat& Filters, int threadnum){
	
	PCA_Out_Result* result = new PCA_Out_Result;
	
	int img_length = InImg.size();
	int mag = (PatchSize - 1) / 2; 
	int channels = InImg[0].channels();
	
	
	cv::Mat img;
	
	vector<int> blockSize;
	vector<int> stepSize;

	for(int i=0; i<2; i++){
		blockSize.push_back(PatchSize);
		stepSize.push_back(1);
	}

	cv::Mat temp;
	cv::Mat mean;
	cv::Mat temp2;
	cv::Mat temp3;

	int coreNum = omp_get_num_procs();//»ñµÃŽŠÀíÆ÷žöÊý
	coreNum = coreNum > threadnum ? threadnum : coreNum;
	cv::Scalar s = cv::Scalar(0);

# pragma omp parallel for default(none) num_threads(coreNum) private(img, temp, temp2, temp3, mean) shared(InImgIdx, s, blockSize, stepSize, mag, img_length, InImg, result, Filters, NumFilters)
	for(int i=0; i<img_length; i++){
		//对样本进行边缘补零操作,以保证映射结果与原图像的尺寸相同。
		cv::copyMakeBorder(InImg[i], img, mag, mag, mag, mag, cv::BORDER_CONSTANT, s);
		
		temp = im2col_general(img, blockSize, stepSize);

		cv::reduce(temp, mean, 0, CV_REDUCE_AVG);
		
		temp3.create(0, temp.cols, temp.type());

		for(int i=0;i<temp.rows;i++){
			temp2 = (temp.row(i) - mean.row(0));
			temp3.push_back(temp2.row(0));
		}
#pragma omp critical
{
	    // 保存图片的索引号。
	    result->OutImgIdx.push_back(InImgIdx[i]);
	    for(int j=0; j<NumFilters; j++)
	    {
			// 减去均值样本矩阵 * 特征向量
			temp = Filters.row(j) * temp3;
			temp = temp.reshape(0, InImg[i].cols);
			result->OutImg.push_back(temp.t());
	    }
}
	}
	/*
	int size = InImgIdx.size();
	for(int i=0; i<size; i++)
		for(int j=0; j<NumFilters; j++)
			result->OutImgIdx.push_back(InImgIdx[i]);*/
	return result;
}

PCA_Train_Result* PCANet_train(vector<cv::Mat>& InImg, PCANet* PcaNet, bool is_extract_feature){
	assert(PcaNet->NumFilters.size() == PcaNet->NumStages);
	
	PCA_Train_Result* train_result = new PCA_Train_Result;
	PCA_Out_Result* out_result = new PCA_Out_Result; 
	PCA_Out_Result* temp;

	out_result->OutImg = InImg;
	int img_length = InImg.size();
	for(int i=0; i<img_length; i++)
		out_result->OutImgIdx.push_back(i);

	int64 e1 = cv::getTickCount();
	int64 eo1, eo2, eb1, eb2;
	
	for(int s=0; s<PcaNet->NumStages; s++){
		eb1 = cv::getTickCount();
		cout << " Computing PCA filter bank and its outputs at stage " << s << "..." << endl;
		train_result->Filters.push_back(PCA_FilterBank(out_result->OutImg, PcaNet->PatchSize, PcaNet->NumFilters[s]));
		eb2 = cv::getTickCount();
		cout <<" stage"<<s<<" PCA_FilterBank time: "<<(eb2 - eb1)/ cv::getTickFrequency()<<endl;

		eo1 = cv::getTickCount();
		if(s != PcaNet->NumStages - 1){
			temp = PCA_output(out_result->OutImg, out_result->OutImgIdx, PcaNet->PatchSize, 
												PcaNet->NumFilters[s], train_result->Filters[s], omp_get_num_procs());
			delete out_result;
			out_result = temp;
		}
		eo2 = cv::getTickCount();
		cout <<" stage"<<s<<" output time: "<<(eo2 - eo1)/ cv::getTickFrequency()<<endl;
	}
	int64 e2 = cv::getTickCount();
	double time = (e2 - e1)/ cv::getTickFrequency();
	cout <<"\n totle FilterBank time: "<<time<<endl;

	InImg.clear();
	vector<cv::Mat>().swap(InImg);

	vector<cv::Mat> tempF;
	int end = PcaNet->NumStages - 1;
	int outIdx_length = out_result->OutImgIdx.size();

	if(is_extract_feature){

		vector<cv::Mat>::const_iterator first = out_result->OutImg.begin();
		vector<cv::Mat>::const_iterator last = out_result->OutImg.begin();
		
		vector<cv::Mat> features;
		Hashing_Result* hashing_r;

		int coreNum = omp_get_num_procs();//»ñµÃŽŠÀíÆ÷žöÊý
		e1 = cv::getTickCount();
# pragma omp parallel for default(none) num_threads(coreNum) private(temp, hashing_r) shared(features, out_result, PcaNet, first, last, outIdx_length, img_length, train_result, end)
		for(int i=0; i<img_length; i++){
			vector<cv::Mat> subInImg(first + i * PcaNet->NumFilters[end], last + (i + 1) * PcaNet->NumFilters[end]);
			vector<int> subIdx;
			/*for(int j=0; j<outIdx_length; j++){
				if(out_result->OutImgIdx[j] == i) subIdx.push_back(1);
				else subIdx.push_back(0);
			}*/
			for(int j=0; j< PcaNet->NumFilters[end]; j++)
				subIdx.push_back(j);
			
			temp = PCA_output(subInImg, subIdx, PcaNet->PatchSize, 
								PcaNet->NumFilters[end], train_result->Filters[end], 2);
			
			hashing_r = HashingHist(PcaNet, temp->OutImgIdx, temp->OutImg);	
			
#pragma omp critical 
{
			features.push_back(hashing_r->Features);
			train_result->feature_idx.push_back(out_result->OutImgIdx[i]);
}
			delete hashing_r;
			delete temp;
			subIdx.clear();
			vector<int>().swap(subIdx);
		}
		e2 = cv::getTickCount();
		time = (e2 - e1)/ cv::getTickFrequency();
		cout <<"\n hasing time: "<<time<<endl;
		
		//out_result->OutImg.clear();
		//vector<cv::Mat>().swap(out_result->OutImg);
		delete out_result;

		int size = features.size();
		if(size > 0){

			train_result->Features.create(0, features[0].cols, features[0].type());
			for(int i=0 ;i<size; i++){
				train_result->Features.push_back(features[i]);
			}

			/*
			train_result->Features = features[0];
			for(int i=1 ;i<size; i++){
				vconcat(train_result->Features, features[i], train_result->Features);
			}*/
		}

		features.clear();
		vector<cv::Mat>().swap(features);
	}
	
	//if(temp != NULL)
	//	delete temp;
	
	return train_result;
}
double round(double r)
{
	return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}
Hashing_Result* HashingHist(PCANet* PcaNet, vector<int>& ImgIdx, vector<cv::Mat>& Imgs)
{
	Hashing_Result* ha_result = new Hashing_Result;

	int length = Imgs.size();
	// 得到最后层的PCA的特征向量数。也就是第二阶段产生的图片数。
	int NumFilters = PcaNet->NumFilters[PcaNet->NumStages - 1];
	// 得到第一层PCA的特征向量数。也就是第一阶段产生的图片数。
	int NumImgin0 = length / NumFilters;

	cv::Mat T;
	int row = Imgs[0].rows;
	int col = Imgs[0].cols;
	int depth = Imgs[0].depth();

	// 这部分就是进行二值化哈希编码时候的权重。
	vector<double> map_weights;
	cv::Mat temp;
	for(int i=NumFilters - 1; i>=0; i--)
	{
		// 权重设置为 2 的 i 次幂。
		map_weights.push_back(pow(2.0,(double)i));
	}

	// 保存 滑动窗口的横向步长和纵向步长。
	vector<int> Ro_BlockSize; 
	// 可以说成是滑动窗口的覆盖率吧。
	double rate = 1 - PcaNet->BlkOverLapRatio;
	for(int i=0; i<PcaNet->HistBlockSize.size(); i++)
	{
		// HistBlockSize是滑动窗口的大小,包括宽和高。
		Ro_BlockSize.push_back(round(PcaNet->HistBlockSize[i] * rate));
	}

	cv::Mat BHist;

	int ImgIdx_length = ImgIdx.size();
	int* new_idx = new int[ImgIdx_length];
	for(int i=0; i<ImgIdx_length; i++)
	{
		new_idx[ImgIdx[i]] = i;
	}

	// PCA的输出一共有64个mat。
	for(int i=0; i<NumImgin0; i++)
	{
		T = cv::Mat::zeros(row,col,depth);

		for(int j=0; j<NumFilters; j++)
		{
			// Heaviside进行二值化处理。
			temp = Heaviside(Imgs[NumFilters * new_idx[i] + j]);
			// 乘以权重,每个像素值都被编码成为(0,255)之间的整数。
			temp = temp * map_weights[j];
			// 之后8个mat进行累加。
			T = T + temp;
		}

		// 第一层的每个输出矩阵,将其分为B块,计算统计每个块的直方图信息,然后在将各个块的直方图特征进行级联,最终得到块扩展直方图特征.
		temp = im2col_general(T, PcaNet->HistBlockSize, Ro_BlockSize);
		temp = Hist(temp, (int)(pow(2.0, NumFilters)) - 1);

		temp = bsxfun_times(temp, NumFilters);

		if(i == 0) BHist = temp;
		else hconcat(BHist,temp,BHist);
	}

	int rows = BHist.rows;
	int cols = BHist.cols;

	ha_result->Features.create(1, rows * cols, BHist.type());

	double *p_Fe = ha_result->Features.ptr<double>(0);
	double *p_Hi;
	for(int i=0; i<rows; i++)
	{
		p_Hi = BHist.ptr<double>(i);
		for(int j=0; j<cols; j++)
		{
			p_Fe[j * rows + i] = p_Hi[j];
		}
	}
	return ha_result;
}

// 进行二值化处理。
cv::Mat Heaviside(cv::Mat& X)
{
	int row = X.rows;
	int col = X.cols;
	int depth = X.depth();

	cv::Mat H(row, col, depth);

	double *p_X, *p_H;

	# pragma omp parallel for default(none) num_threads(4) private(p_X, p_H) shared(X, H, row, col)
    // 对于X的每一行中的每一列的元素,如果>0,则变为1;否则为0。
	for(int i=0; i<row; i++)
	{
		p_X = X.ptr<double>(i);
		p_H = H.ptr<double>(i);

		for(int j=0; j<col; j++)
		{
			if(p_X[j]>0) p_H[j] = 1;
			else p_H[j] = 0;
		}
	}

	return H;
}

// 获得直方图
cv::Mat Hist(cv::Mat& mat, int Range)
{
	cv::Mat temp = mat.t();
	int row = temp.rows;
	int col = temp.cols;
	int depth = temp.depth();

	cv::Mat Hist = cv::Mat::zeros(row, Range + 1,depth);

	double *p_M, *p_H;

	# pragma omp parallel for default(none) num_threads(4) private(p_M, p_H) shared(temp, Hist, row, col)
    for(int i=0; i<row; i++)
	{
		p_M = temp.ptr<double>(i);
		p_H = Hist.ptr<double>(i);

		for(int j=0; j<col; j++)
		{
			p_H[(int)p_M[j]] += 1;
		}
	}
	temp = Hist.t();
	return temp;
}

// 直方图特征。
cv::Mat bsxfun_times(cv::Mat& BHist, int NumFilters)
{
	double *p_BHist;
	int row = BHist.rows;
	int col = BHist.cols;

	double* sum = new double[col];
	// 初始化sum。
	for(int i=0; i<col; i++)
	{
		sum[i] = 0;
	}

	// 计算所有行元素的相加和。
	for(int i=0; i<row; i++)
	{
		p_BHist = BHist.ptr<double>(i);
		for(int j=0; j<col; j++)
		{
			sum[j] += p_BHist[j];
		}
	}
	double p = pow(2.0, NumFilters);
	# pragma omp parallel for default(none) num_threads(4) shared(col, sum, p)
    for(int i=0; i<col; i++)
	{
		sum[i] = p/sum[i];
	}

	# pragma omp parallel for default(none) num_threads(4) private(p_BHist) shared(col, row, sum, BHist)
   // 对于每行元素乘以sum。
	for(int i=0; i<row; i++)
	{
		p_BHist = BHist.ptr<double>(i);
		for(int j=0; j<col; j++)
		{
			p_BHist[j] *= sum[j];
		}
	}
	return BHist;
}
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MachineLP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值