最近学了cuda,准备找个项目来练练手。先分析了一把代码,发现主要性能瓶颈在cv::pyrMeanShiftFiltering,原本打算自己撸一个gpu版的,没想到一搜发现已经有人写好了cuda::meanShiftSegmentation。那就改变计划,读读源码吧。
mean shift
mean shift简单理解就是划一个圈圈,算出mean,然后往这个方向shift过去。
图片来自opencv官方文档
思路还是挺简单、直观。但是具体到应用的时候,就可以发挥各种脑洞去改造了。
我之前用meanshift是用来聚类后自动划档,比如统计购买同一个商品的用户的注册时间,可以按新老用户划分出几个人群。因为每一个商品的用户分布都不一致,不能直接拍脑袋定一个固定值,聚成多少个簇也是不固定的。而meanshift就可以很方便解决划分多少个档位,怎么划分的问题。
那回到图像的问题,图片是rows*cols个像素,跟opencv举例子的那个图的情况好像不一样,图片怎么meanshift呢?图像有颜色空间(R,G,B)和位置空间(x,y)5维,我之前还以为是直接对颜色空间进行meanshift,其实不是的。
那先来看看cv::pyrMeanShiftFiltering是怎么处理的吧。
cv::pyrMeanShiftFiltering源码
pyr是指金字塔pyramids,那金字塔的meanshift又是怎么实现呢?
/****************************************************************************************\
* Meanshift *
\****************************************************************************************/
void cv::pyrMeanShiftFiltering( InputArray _src, OutputArray _dst,
double sp0, double sr, int max_level,
TermCriteria termcrit )
{
CV_INSTRUMENT_REGION()
Mat src0 = _src.getMat();
if( src0.empty() )
return;
_dst.create( src0.size(), src0.type() );
Mat dst0 = _dst.getMat();
// cn=channel num?
const int cn = 3;
const int MAX_LEVELS = 8;
if( (unsigned)max_level > (unsigned)MAX_LEVELS )
CV_Error( CV_StsOutOfRange, "The number of pyramid levels is too large or negative" );
std::vector<cv::Mat> src_pyramid(max_level+1);
std::vector<cv::Mat> dst_pyramid(max_level+1);
cv::Mat mask0;
int i, j, level;
//uchar* submask = 0;
//(c0,c1,c2) 跟 (ofs0,ofs1,ofs2)的颜色欧式距离的平方。用平方就不用开根号了
#define cdiff(ofs0) (tab[c0-dptr[ofs0]+255] + \
tab[c1-dptr[(ofs0)+1]+255] + tab[c2-dptr[(ofs0)+2]+255] >= isr22)
double sr2 = sr * sr;
// i是指integer, isr2^2为啥最小要16?因为sr至少要2
int isr2 = cvRound(sr2), isr22 = MAX(isr2,16);
int tab[768];
if( src0.type() != CV_8UC3 )
CV_Error( CV_StsUnsupportedFormat, "Only 8-bit, 3-channel images are supported" );
if( src0.type() != dst0.type() )
CV_Error( CV_StsUnmatchedFormats, "The input and output images must have the same type" );
if( src0.size() != dst0.size() )
CV_Error( CV_StsUnmatchedSizes, "The input