好久没更博客了。去年年底跳了槽,转回了工业检测领域,忙于找工作和开发新算法,同时担心泄露技术秘密,所以一直没有更新博客。刚过去的三个月试用期里开发了两个原创算法,一个已经申请专利(涉及工业品区域边界轮廓提取),一个正准备申请(涉及复杂纹理下的圆孔提取),也算是值得骄傲的一件事了。待专利授权下来后,我可以给大家讲一讲这两个新算法,目前就暂且保密啦。言归正传,下面给大家分享一个个人编写的一些基础算法的代码。
引导滤波和边窗滤波都是近几年出现的快速高效好用的滤波器。引导滤波器可以让输入图像学习引导图像的纹理信息,具有O(N)的计算复杂度,当引导图像就是输入图像本身时,引导滤波还能起到保边滤波的作用。边窗滤波则是采用半窗口甚至1/4窗口内的像素值滤波来代替常规的全窗口滤波,通过选择枚举的8个半窗口与1/4窗口滤波器滤波结果中与原始值最接近的一个来作为当前点滤波结果,可以有效避免在边缘处因为全窗口跨越两个截然不同的区域所造成的边缘扩散问题,达到保边效果,方法非常简单直观,效果却出类拔萃。这种重视捕捉直觉并将其抽象出来的算法开发思想值得我们学习。这里本人分享一下本人实现的这两种滤波器的代码以帮助大家避免不必要的重复劳动(国内太缺乏优质博客与开源精神了)。其中,引导滤波只实现了单通道版,三通道版需要用到协方差,这个工作就留给各位读者了(头文件读者自己添加吧)。
guidedFilter代码:
Mat guidedFilter(const cv::Mat& inputImg, const cv::Mat& guideImg, int iBoxFiltRadius, float eps)
{
Mat inputImgF, guideImgF;
inputImg.convertTo(inputImgF, CV_32F);
guideImg.convertTo(guideImgF, CV_32F);
Mat productImg = inputImgF.mul(guideImgF);
Mat onesImg = Mat::ones(inputImgF.size(), CV_32F);
Mat bfOnesImg, bfInputImg, bfGuideImg, bfProductImg, bfSqureGuideImg;
boxFilter(onesImg, bfOnesImg, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
boxFilter(inputImgF, bfInputImg, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
boxFilter(guideImgF, bfGuideImg, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
boxFilter(productImg, bfProductImg, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
boxFilter(guideImgF.mul(guideImgF), bfSqureGuideImg, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
divide(bfInputImg, bfOnesImg, bfInputImg);
divide(bfGuideImg, bfOnesImg, bfGuideImg);
divide(bfProductImg, bfOnesImg, bfProductImg);
divide(bfSqureGuideImg, bfOnesImg, bfSqureGuideImg);
//mean(I*G)-mean(I)*mean(G)及mean(G*G)-mean(G)*mean(G)
Mat varIG = bfProductImg - bfInputImg.mul(bfGuideImg);
Mat varG = bfSqureGuideImg - bfGuideImg.mul(bfGuideImg);
//a=VarIG/(VarI+eps); b=mean(inputImgF)-a*mean(guideImgF)
Mat a, b;
divide(varIG, varG + eps, a);
b = bfInputImg - a.mul(bfGuideImg);
Mat bfA, bfB;
boxFilter(a, bfA, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
boxFilter(b, bfB, CV_32F, Size(2 * iBoxFiltRadius + 1, 2 * iBoxFiltRadius + 1), Point(-1, -1), false);
divide(bfA, bfOnesImg, bfA);
divide(bfB, bfOnesImg, bfB);
Mat filtedImg = bfA.mul(guideImgF) + bfB;
if (inputImg.type() == CV_32F)
{
return filtedImg;
}
else
{
Mat outputImg;
filtedImg.convertTo(outputImg, inputImg.type());
return outputImg;
}
}
sideWindowBoxFilter代码:
void SideWindowBoxFilter(Mat& srcImg, Mat& dstImg, int r = 4, int interation = 1)
{
int channels = srcImg.channels();
int height = srcImg.rows;
int width = srcImg.cols;
Mat disImg = Mat::zeros(Size(width, height), CV_32F);
Mat disImgArr[8] = { disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone() };
vector vecChannels;
if (channels > 1)
{
split(srcImg, vecChannels);
}
else
{
vecChannels.push_back(srcImg.clone());
}
vector vecDstChannels;
//构造滤波器
Mat kU = Mat::zeros(2 * r + 1, 1, CV_32F);
kU(Range(0, r + 1), Range(0, 1)) = 1;
Mat kD = Mat::zeros(2 * r + 1, 1, CV_32F);
kD(Range(r, 2 * r + 1), Range(0, 1)) = 1;
Mat kL = Mat::zeros(1, 2 * r + 1, CV_32F);
kL(Range(0, 1), Range(0, r + 1)) = 1;
Mat kR = Mat::zeros(1, 2 * r + 1, CV_32F);
kR(Range(0, 1), Range(r, 2 * r + 1)) = 1;
Mat AllOneKerUD = Mat::ones(2 * r + 1, 1, CV_32F);
Mat AllOneKerLR = Mat::ones(1, 2 * r + 1, CV_32F);
Mat filtedImg0, filtedImg1, filtedImg2, filtedImg3, filtedImg4, filtedImg5, filtedImg6, filtedImg7;
Mat filtedImgArr[8] = { disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone(),disImg.clone() };
for (int ch = 0; ch < channels; ch++)
{
Mat chImg = vecChannels[ch];
if (chImg.type() != CV_32F)
{
chImg.convertTo(chImg, CV_32F);
}
Mat padChImg;
copyMakeBorder(chImg, padChImg, r, r, r, r, BORDER_REPLICATE);
for (int iter = 0; iter < interation; iter++)
{
sepFilter2D(padChImg, filtedImg0, CV_32F, kL, kU, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[0] = filtedImg0 / ((r + 1)*(r + 1));
disImgArr[0] = abs(filtedImgArr[0] - padChImg);
sepFilter2D(padChImg, filtedImg1, CV_32F, kL, kD, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[1] = filtedImg1 / ((r + 1)*(r + 1));
disImgArr[1] = abs(filtedImgArr[1] - padChImg);
sepFilter2D(padChImg, filtedImg2, CV_32F, kR, kU, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[2] = filtedImg2 / ((r + 1)*(r + 1));
disImgArr[2] = abs(filtedImgArr[2] - padChImg);
sepFilter2D(padChImg, filtedImg3, CV_32F, kR, kD, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[3] = filtedImg3 / ((r + 1)*(r + 1));
disImgArr[3] = abs(filtedImgArr[3] - padChImg);
sepFilter2D(padChImg, filtedImg4, CV_32F, AllOneKerLR, kU, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[4] = filtedImg4 / ((r + 1)*(2 * r + 1));
disImgArr[4] = abs(filtedImgArr[4] - padChImg);
sepFilter2D(padChImg, filtedImg5, CV_32F, AllOneKerLR, kD, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[5] = filtedImg5 / ((r + 1)*(2 * r + 1));
disImgArr[5] = abs(filtedImgArr[5] - padChImg);
sepFilter2D(padChImg, filtedImg6, CV_32F, kL, AllOneKerUD, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[6] = filtedImg6 / ((r + 1)*(2 * r + 1));
disImgArr[6] = abs(filtedImgArr[6] - padChImg);
sepFilter2D(padChImg, filtedImg7, CV_32F, kR, AllOneKerUD, Point(-1, -1), 0, BORDER_CONSTANT);
filtedImgArr[7] = filtedImg7 / ((r + 1)*(2 * r + 1));
disImgArr[7] = abs(filtedImgArr[7] - padChImg);
for (int row = 0; row < padChImg.rows; row++)
{
for (int col = 0; col < padChImg.cols; col++)
{
int idxMin = 0;
float minVal = FLT_MAX;
for (int k = 0; k < 8; k++)
{
if (disImgArr[k].at(row, col) < minVal)
{
minVal = disImgArr[k].at(row, col);
idxMin = k;
}
}
padChImg.at(row, col) = filtedImgArr[idxMin].at(row, col);
}
}
}
padChImg(Range(r, padChImg.rows - r), Range(r, padChImg.cols - r)).copyTo(chImg);
if (vecChannels[ch].type() != CV_32F)
{
chImg.convertTo(chImg, vecChannels[ch].type());
}
vecDstChannels.push_back(chImg);
}
if (channels > 1)
{
merge(vecDstChannels, dstImg);
}
else
{
dstImg = vecDstChannels[0];
}
}
输入图像:
灰度图:
引导滤波处理结果(半径:60,eps:0.001*255^2):
处理前后差值图像(放大了30倍):
可以看到,头发丝、衣服边缘等锐利边缘改变得很少,较平滑的区域和小尺度纹理改变得较多,符合保边滤波的要求。
边窗滤波结果(半径:9):
保边滤波效果显而易见。