#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的参考图片
};
#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;
}
// 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。