局部二值相似模式(LBSP)


1. LBSP.h

#pragma once

#include 
#include 
#include 
#include "DistanceUtils.h"

/*!
	Local Binary Similarity Pattern (LBSP) feature extractor

	Note 1: both grayscale and RGB/BGR images may be used with this extractor.
	Note 2: using LBSP::compute2(...) is logically equivalent to using LBSP::compute(...) followed by LBSP::reshapeDesc(...).

	For more details on the different parameters, see G.-A. Bilodeau et al, "Change Detection in Feature Space Using Local
	Binary Similarity Patterns", in CRV 2013.

	This algorithm is currently NOT thread-safe.
 */
class LBSP : public cv::DescriptorExtractor {
public:
	//! constructor 1, threshold = absolute intensity 'similarity' threshold used when computing comparisons
	LBSP(size_t nThreshold);
	//! constructor 2, threshold = relative intensity 'similarity' threshold used when computing comparisons
	LBSP(float fRelThreshold, size_t nThresholdOffset=0);
	//! default destructor
	virtual ~LBSP();
	//! loads extractor params from the specified file node @@@@ not impl
	virtual void read(const cv::FileNode&);
	//! writes extractor params to the specified file storage @@@@ not impl
	virtual void write(cv::FileStorage&) const;
	//! sets the 'reference' image to be used for inter-frame comparisons (note: if no image is set or if the image is empty, the algorithm will default back to intra-frame comparisons)
	virtual void setReference(const cv::Mat&);
	//! returns the current descriptor size, in bytes
	virtual int descriptorSize() const;
	//! returns the current descriptor data type
	virtual int descriptorType() const;
	//! returns whether this extractor is using a relative threshold or not
	virtual bool isUsingRelThreshold() const;
	//! returns the current relative threshold used for comparisons (-1 = invalid/not used)
	virtual float getRelThreshold() const;
	//! returns the current absolute threshold used for comparisons (-1 = invalid/not used)
	virtual size_t getAbsThreshold() const;

	//! similar to DescriptorExtractor::compute(const cv::Mat& image, ...), but in this case, the descriptors matrix has the same shape as the input matrix (possibly slower, but the result can be displayed)
	void compute2(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const;
	//! batch version of LBSP::compute2(const cv::Mat& image, ...), also similar to DescriptorExtractor::compute(const std::vector& imageCollection, ...)
	void compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const;

	//! utility function, shortcut/lightweight/direct single-point LBSP computation function for extra flexibility (1-channel version)
	inline static void computeGrayscaleDescriptor(const cv::Mat& oInputImg, const uchar _ref, const int _x, const int _y, const size_t _t, ushort& _res) {
		CV_DbgAssert(!oInputImg.empty());
		CV_DbgAssert(oInputImg.type()==CV_8UC1);
		CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size
		CV_DbgAssert(_x>=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2);
		CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2);
		CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2);
		CV_DbgAssert(_x=(int)LBSP::PATCH_SIZE/2 && _y>=(int)LBSP::PATCH_SIZE/2);
		CV_DbgAssert(_x& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput);
	//! utility function, used to illustrate the difference between two descriptor images
	static void calcDescImgDiff(const cv::Mat& oDesc1, const cv::Mat& oDesc2, cv::Mat& oOutput, bool bForceMergeChannels=false);
	//! utility function, used to filter out bad keypoints that would trigger out of bounds error because they're too close to the image border
	static void validateKeyPoints(std::vector& voKeypoints, cv::Size oImgSize);
	//! utility function, used to filter out bad pixels in a ROI that would trigger out of bounds error because they're too close to the image border
	static void validateROI(cv::Mat& oROI);
	//! utility, specifies the pixel size of the pattern used (width and height)
	static const size_t PATCH_SIZE = 5;
	//! utility, specifies the number of bytes per descriptor (should be the same as calling 'descriptorSize()')
	static const size_t DESC_SIZE = 2;    //LBSP描述子是16位,即2个字节

protected:
	//! classic 'compute' implementation, based on the regular DescriptorExtractor::computeImpl arguments & expected output
	virtual void computeImpl(const cv::Mat& oImage, std::vector& voKeypoints, cv::Mat& oDescriptors) const;

	const bool m_bOnlyUsingAbsThreshold; //绝对阈值
	const float m_fRelThreshold;         //相对阈值
	const size_t m_nThreshold;
	cv::Mat m_oRefImage;                 //计算inter-frame comparisons的参考图片
};

2. LBSP.cpp

#include "LBSP.h"

LBSP::LBSP(size_t nThreshold)
	:	 m_bOnlyUsingAbsThreshold(true)
		,m_fRelThreshold(0) // unused
		,m_nThreshold(nThreshold)
		,m_oRefImage() {}

LBSP::LBSP(float fRelThreshold, size_t nThresholdOffset)
	:	 m_bOnlyUsingAbsThreshold(false)
		,m_fRelThreshold(fRelThreshold)
		,m_nThreshold(nThresholdOffset)
		,m_oRefImage() {
	CV_Assert(m_fRelThreshold>=0);
}

LBSP::~LBSP() {}

void LBSP::read(const cv::FileNode& /*fn*/) {
    // ... = fn["..."];
}

void LBSP::write(cv::FileStorage& /*fs*/) const {
    //fs << "..." << ...;
}

void LBSP::setReference(const cv::Mat& img) {
	CV_DbgAssert(img.empty() || img.type()==CV_8UC1 || img.type()==CV_8UC3);
	m_oRefImage = img;
}

//LBSP描述子是16位,即2个字节
int LBSP::descriptorSize() const {
	return DESC_SIZE;
}

int LBSP::descriptorType() const {
	return CV_16U;
}

bool LBSP::isUsingRelThreshold() const {
	return !m_bOnlyUsingAbsThreshold;
}

float LBSP::getRelThreshold() const {
	return m_fRelThreshold;
}

size_t LBSP::getAbsThreshold() const {
	return m_nThreshold;
}

//使用绝对阈值计算LBSP
static inline void lbsp_computeImpl(	const cv::Mat& oInputImg,						//需要计算LBSP的输入图片
										const cv::Mat& oRefImg,							//计算LBSP的参考图片,也就是使用的各个位置的中间值
										const std::vector& voKeyPoints,	//去除边缘后的有效点
										cv::Mat& oDesc,								    //存放计算好的LBSP值
										size_t _t) {								    //_t是绝对阈值
	CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type()));
	CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3);
	CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size
	const size_t nChannels = (size_t)oInputImg.channels();
	const size_t _step_row = oInputImg.step.p[0];
	const uchar* _data = oInputImg.data;
	const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data;
	const size_t nKeyPoints = voKeyPoints.size();
	if(nChannels==1) {
		oDesc.create((int)nKeyPoints,1,CV_16UC1);
		for(size_t k=0; k((int)k);         //存放计算的LBSP结果值
			#include "LBSP_16bits_dbcross_1ch.i"             //通过该文件具体计算每一个像素位置的LBSP值
		}
	}
	else { //nChannels==3
		oDesc.create((int)nKeyPoints,1,CV_16UC3);
		for(size_t k=0; k& voKeyPoints,
										cv::Mat& oDesc,
										float fThreshold,
										size_t nThresholdOffset) {
	CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type()));
	CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3);
	CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size
	CV_DbgAssert(fThreshold>=0);
	const size_t nChannels = (size_t)oInputImg.channels();
	const size_t _step_row = oInputImg.step.p[0];
	const uchar* _data = oInputImg.data;
	const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data;
	const size_t nKeyPoints = voKeyPoints.size();
	if(nChannels==1) {
		oDesc.create((int)nKeyPoints,1,CV_16UC1);
		for(size_t k=0; k((int)k);                //存放计算的LBSP结果值
			const size_t _t = (size_t)(_ref*fThreshold)+nThresholdOffset;
			#include "LBSP_16bits_dbcross_1ch.i"
		}
	}
	else { //nChannels==3
		oDesc.create((int)nKeyPoints,1,CV_16UC3);
		for(size_t k=0; k& voKeyPoints,
										cv::Mat& oDesc,
										size_t _t) {
	CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type()));
	CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3);
	CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size
	const size_t nChannels = (size_t)oInputImg.channels();
	const size_t _step_row = oInputImg.step.p[0];
	const uchar* _data = oInputImg.data;
	const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data;
	const size_t nKeyPoints = voKeyPoints.size();
	if(nChannels==1) {
		oDesc.create(oInputImg.size(),CV_16UC1);
		for(size_t k=0; k(_y,_x);
			#include "LBSP_16bits_dbcross_1ch.i"
		}
	}
	else { //nChannels==3
		oDesc.create(oInputImg.size(),CV_16UC3);
		for(size_t k=0; k& voKeyPoints,
										cv::Mat& oDesc,
										float fThreshold,
										size_t nThresholdOffset) {
	CV_DbgAssert(oRefImg.empty() || (oRefImg.size==oInputImg.size && oRefImg.type()==oInputImg.type()));
	CV_DbgAssert(oInputImg.type()==CV_8UC1 || oInputImg.type()==CV_8UC3);
	CV_DbgAssert(LBSP::DESC_SIZE==2); // @@@ also relies on a constant desc size
	CV_DbgAssert(fThreshold>=0);
	const size_t nChannels = (size_t)oInputImg.channels();
	const size_t _step_row = oInputImg.step.p[0];
	const uchar* _data = oInputImg.data;
	const uchar* _refdata = oRefImg.empty()?oInputImg.data:oRefImg.data;
	const size_t nKeyPoints = voKeyPoints.size();
	if(nChannels==1) {
		oDesc.create(oInputImg.size(),CV_16UC1);
		for(size_t k=0; k(_y,_x);
			const size_t _t = (size_t)(_ref*fThreshold)+nThresholdOffset;
			#include "LBSP_16bits_dbcross_1ch.i"
		}
	}
	else { //nChannels==3
		oDesc.create(oInputImg.size(),CV_16UC3);
		for(size_t k=0; k& voKeypoints, cv::Mat& oDescriptors) const {
	CV_Assert(!oImage.empty());
    cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImage.size(),PATCH_SIZE/2);             //去除图像边界的像素点
    cv::KeyPointsFilter::runByKeypointSize(voKeypoints,std::numeric_limits::epsilon()); //去除超出一定范围的像素点
    if(voKeypoints.empty()) {
        oDescriptors.release();
        return;
    }
	if(m_bOnlyUsingAbsThreshold)
		lbsp_computeImpl2(oImage,m_oRefImage,voKeypoints,oDescriptors,m_nThreshold);
	else
		lbsp_computeImpl2(oImage,m_oRefImage,voKeypoints,oDescriptors,m_fRelThreshold,m_nThreshold);
}

void LBSP::compute2(const std::vector& voImageCollection, std::vector >& vvoPointCollection, std::vector& voDescCollection) const {
    CV_Assert(voImageCollection.size() == vvoPointCollection.size());
    voDescCollection.resize(voImageCollection.size());
    for(size_t i=0; i& voKeypoints, cv::Mat& oDescriptors) const {
	CV_Assert(!oImage.empty());
	cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImage.size(),PATCH_SIZE/2);
	cv::KeyPointsFilter::runByKeypointSize(voKeypoints,std::numeric_limits::epsilon());
	if(voKeypoints.empty()) {
		oDescriptors.release();
		return;
	}
	if(m_bOnlyUsingAbsThreshold)
		lbsp_computeImpl(oImage,m_oRefImage,voKeypoints,oDescriptors,m_nThreshold);
	else
		lbsp_computeImpl(oImage,m_oRefImage,voKeypoints,oDescriptors,m_fRelThreshold,m_nThreshold);
}

//把所有计算好的LBSP描述子恢复为矩阵的形式,即把一行转变成与图片等高等宽的图片形式
void LBSP::reshapeDesc(cv::Size oSize, const std::vector& voKeypoints, const cv::Mat& oDescriptors, cv::Mat& oOutput) {
	CV_DbgAssert(!voKeypoints.empty());
	CV_DbgAssert(!oDescriptors.empty() && oDescriptors.cols==1);
	CV_DbgAssert(oSize.width>0 && oSize.height>0);
	CV_DbgAssert(DESC_SIZE==2); // @@@ also relies on a constant desc size
	CV_DbgAssert(oDescriptors.type()==CV_16UC1 || oDescriptors.type()==CV_16UC3);
	const size_t nChannels = (size_t)oDescriptors.channels();
	const size_t nKeyPoints = voKeypoints.size();
	if(nChannels==1) {
		oOutput.create(oSize,CV_16UC1);
		oOutput = cv::Scalar_(0);
		for(size_t k=0; k(voKeypoints[k].pt) = oDescriptors.at((int)k);
	}
	else { //nChannels==3
		oOutput.create(oSize,CV_16UC3);
		oOutput = cv::Scalar_(0,0,0);
		for(size_t k=0; k(i,j) = (uchar)(fScaleFactor*hdist(desc1_ptr[j],desc2_ptr[j]));//! hdist  :computes the hamming distance between two N-byte vectors using an 8-bit popcount LUT
		}
	}
	else { //nChannels==3
		if(bForceMergeChannels)
			oOutput.create(oDesc1.size(),CV_8UC1);
		else
			oOutput.create(oDesc1.size(),CV_8UC3);
		oOutput = cv::Scalar::all(0);
		for(int i=0; i& voKeypoints, cv::Size oImgSize) {
	cv::KeyPointsFilter::runByImageBorder(voKeypoints,oImgSize,PATCH_SIZE/2);
}
//去除感兴趣区域的边界
void LBSP::validateROI(cv::Mat& oROI) {
	CV_Assert(!oROI.empty() && oROI.type()==CV_8UC1);
	cv::Mat oROI_new(oROI.size(),CV_8UC1,cv::Scalar_(0));
	const size_t nBorderSize = PATCH_SIZE/2;
	const cv::Rect nROI_inner(nBorderSize,nBorderSize,oROI.cols-nBorderSize*2,oROI.rows-nBorderSize*2);
	cv::Mat(oROI,nROI_inner).copyTo(cv::Mat(oROI_new,nROI_inner));
	oROI = oROI_new;
}

3. LBSP_16bits_dbcross_1ch.i

// note: this is the LBSP 16 bit double-cross single channel pattern as used in
// the original article by G.-A. Bilodeau et al.
// 
//  O   O   O          4 ..  3 ..  6
//    O O O           .. 15  8 13 ..
//  O O X O O    =>    0  9  X 11  1
//    O O O           .. 12 10 14 ..
//  O   O   O          7 ..  2 ..  5
//
//
// must be defined externally:
//      _t              (size_t, absolute threshold used for comparisons)
//      _ref            (uchar, 'central' value used for comparisons)
//      _data           (uchar*, single-channel data to be covered by the pattern)
//      _y              (int, pattern rows location in the image data)
//      _x              (int, pattern cols location in the image data)
//      _step_row       (size_t, step size between rows, including padding)
//      _res            (ushort, 16 bit result vector)
//       L1dist         (function, returns the absolute difference between two uchars)

#ifdef _val
#error "definitions clash detected"
#else
#define _val(x,y) _data[_step_row*(_y+y)+_x+x]
#endif

_res = ((L1dist(_val(-1, 1),_ref) > _t) << 15)
     + ((L1dist(_val( 1,-1),_ref) > _t) << 14)
     + ((L1dist(_val( 1, 1),_ref) > _t) << 13)
     + ((L1dist(_val(-1,-1),_ref) > _t) << 12)
     + ((L1dist(_val( 1, 0),_ref) > _t) << 11)
     + ((L1dist(_val( 0,-1),_ref) > _t) << 10)
     + ((L1dist(_val(-1, 0),_ref) > _t) << 9)
     + ((L1dist(_val( 0, 1),_ref) > _t) << 8)
     + ((L1dist(_val(-2,-2),_ref) > _t) << 7)
     + ((L1dist(_val( 2, 2),_ref) > _t) << 6)
     + ((L1dist(_val( 2,-2),_ref) > _t) << 5)
     + ((L1dist(_val(-2, 2),_ref) > _t) << 4)
     + ((L1dist(_val( 0, 2),_ref) > _t) << 3)
     + ((L1dist(_val( 0,-2),_ref) > _t) << 2)
     + ((L1dist(_val( 2, 0),_ref) > _t) << 1)
     + ((L1dist(_val(-2, 0),_ref) > _t));

#undef _val


参考文献:

完整的LBSP源码请点这里:https://bitbucket.org/pierre_luc_st_charles/lobster/src。

你可能感兴趣的:(opencv源代码分析)