C++ OpenCV实现boxfilter方框滤波的方法详解
作者:拜阳
box filter简单解释
box filter的作用很简单,即对局部区域求平均,并把值赋给某个点,一般我们赋给区域中心。用公式表达如下:
其中patch是以(row,col)为中心的一块区域。
为了跟后面的公式及程序对应,我们做如下定义:
- r:patch的半径。半径在宽高方向可以不相等,但是本文目的不在于对半径的处理,所以简单起见设为相等。
- n:patch的长度,等于(2∗r+1)。
- (rows,cols):图像的尺寸,行数和列数。
- (row,col):对完整图像的索引。
- (i,j):对图像patch的索引
- k:对通道的索引。
1. 暴力实现——四循环
外层两个循环是关于完整图像(row,col)的循环,内层两个循环是关于图像patch(i,j)的循环。
注意:如果图像是多通道的话实际上还有一个通常维度的循环,但是通道数不是本文优化的重心,所以本文不再赘述这个因素,后文也不再提,并且在计算量的估计中也会把这个因素省略掉。
这个实现比较简单,需要做的计算有:
- rows∗cols∗n∗n次加法,内层循环的计算量o(n2),非常大。
- rows∗cols次除法:除法为了求平均
2. 行列分离
patch的平均可以进行行列分离,也就是先对行方向做平均,并缓存结果,再对缓存的结果做列方向的平均。以公式的形式表达如下:
举个例子展开写会容易理解,比如3*3的patch,共9个数:
这种方式的计算量:
- 2∗rows∗cols∗n次加法,相对于暴力版本,内层循环降低了一个数量级的算力,变成o(n)了
- 2∗rows∗cols次除法
3. 行列分离优化版
第二种实现可以对求和做进一步优化。在单个维度做求和时,可以对当前一维patch的和做一个缓存,当中心点移动后,减去弹出像素的值,加上新增像素的值,这样就避免了重复性求和操作。
这种方案需要对patch的和做一个初始化和缓存,该方案的计算量为:
- 2∗rows∗cols次减法,2∗rows∗cols次加法,内层循环的计算变为o(1)了,进一步降低了一个数量级算力。
- 2∗rows∗cols次除法
代码
上面做计算量估计的时候没有考虑边界条件,在具体代码实现的时候需要仔细处理边界,防止数组访问越界。
代码同时跟opencv做了个效果和性能的对比,第三种方式虽然仍然比opencv慢,但性能基本处于同一量级了,opencv可能还做了一些其他跟算法无关的优化,比如指令集、并行化之类的。
注意:下面为了方便比较,opencv boxFilter的边界处理参数选择BORDER_CONSTANT。即使是边界处patch不满覆盖的情况下,opencv仍然除以n2 ,也就是说除以的数字有点大了,所以边界会逐渐发黑,特别是kernel_size(对应于radius)比较大时候视觉效果更明显。
#include <opencv2/opencv.hpp> #include <iostream> #include <vector> #include <string> #include <ctime> using namespace std; using namespace cv; Mat BoxFilter_1(const Mat& image, int radius); Mat BoxFilter_2(const Mat& image, int radius); Mat BoxFilter_3(const Mat& image, int radius); int main() { clock_t time_beg; clock_t time_end; Mat image = imread("lena_std.bmp", IMREAD_UNCHANGED); image.convertTo(image, CV_32FC3); image /= 255.0f; int radius = 9; int ksize = radius * 2 + 1; Mat image_box_filter_cv; time_beg = clock(); boxFilter(image, image_box_filter_cv, -1, Size(ksize, ksize), Point(-1, -1), true, BORDER_CONSTANT); time_end = clock(); cout << "box-filter-cv time cost: " << time_end - time_beg << endl; Mat image_box_filter_1 = BoxFilter_1(image, radius); Mat image_box_filter_2 = BoxFilter_2(image, radius); Mat image_box_filter_3 = BoxFilter_3(image, radius); namedWindow("original_image", 1); imshow("original_image", image); namedWindow("cv_box_filter", 1); imshow("cv_box_filter", image_box_filter_cv); namedWindow("box_filter-1", 1); imshow("box_filter-1", image_box_filter_1); namedWindow("box_filter-2", 1); imshow("box_filter-2", image_box_filter_2); namedWindow("box_filter-3", 1); imshow("box_filter-3", image_box_filter_3); Mat diff; cv::absdiff(image_box_filter_2, image_box_filter_3, diff); namedWindow("diff", 1); imshow("diff", 50 * diff); waitKey(0); destroyAllWindows(); return 0; } Mat BoxFilter_1(const Mat& image, int radius) { int cols = image.cols; int rows = image.rows; int channels = image.channels(); int row_bound = rows - 1; int col_bound = cols - 1; Mat result(rows, cols, CV_32FC3); clock_t time_beg; clock_t time_end; time_beg = clock(); for (int row = 0; row < rows; ++row) { int row_beg = max(row - radius, 0); int row_end = min(row + radius, row_bound); for (int col = 0; col < cols; ++col) { int col_beg = max(col - radius, 0); int col_end = min(col + radius, col_bound); vector<float> sums(channels, 0.0f); int count = 0; for (int i = row_beg; i <= row_end; ++i) { for (int j = col_beg; j <= col_end; ++j) { count++; for (int k = 0; k < channels; ++k) { sums[k] += image.at<Vec3f>(i, j)[k]; } } } for (int k = 0; k < channels; ++k) { result.at<Vec3f>(row, col)[k] = sums[k] / static_cast<float>(count); // opencv BORDER_CONSTANT: /*float COUNT = (float)(2 * radius + 1) * (2 * radius + 1); result.at<Vec3f>(row, col)[k] = sums[k] / COUNT;*/ } } } result = cv::max(cv::min(result, 1.0), 0.0); time_end = clock(); cout << "box-filter-1 time cost: " << time_end - time_beg << endl; return result; } Mat BoxFilter_2(const Mat& image, int radius) { int cols = image.cols; int rows = image.rows; int channels = image.channels(); int row_bound = rows - 1; int col_bound = cols - 1; Mat result(rows, cols, CV_32FC3); clock_t time_beg; clock_t time_end; time_beg = clock(); // compute mean for row-wise Mat row_result(rows, cols, CV_32FC3); for (int row = 0; row < rows; ++row) { for (int col = 0; col < cols; ++col) { int col_beg = max(col - radius, 0); int col_end = min(col + radius, col_bound); vector<float> sums(channels, 0.0f); int count = 0; for (int j = col_beg; j <= col_end; ++j) { count++; for (int k = 0; k < channels; ++k) { sums[k] += image.at<Vec3f>(row, j)[k]; } } for (int k = 0; k < channels; ++k) { row_result.at<Vec3f>(row, col)[k] = sums[k] / static_cast<float>(count); } } } // compute mean for column-wise for (int col = 0; col < cols; ++col) { for (int row = 0; row < rows; ++row) { int row_beg = max(row - radius, 0); int row_end = min(row + radius, row_bound); vector<float> sums(channels, 0.0f); int count = 0; for (int i = row_beg; i <= row_end; ++i) { count++; for (int k = 0; k < channels; ++k) { sums[k] += row_result.at<Vec3f>(i, col)[k]; } } for (int k = 0; k < channels; ++k) { result.at<Vec3f>(row, col)[k] = sums[k] / static_cast<float>(count); } } } result = cv::max(cv::min(result, 1.0), 0.0); time_end = clock(); cout << "box-filter-2 time cost: " << time_end - time_beg << endl; return result; } Mat BoxFilter_3(const Mat& image, int radius) { int cols = image.cols; int rows = image.rows; int channels = image.channels(); Mat result(rows, cols, CV_32FC3); clock_t time_beg; clock_t time_end; time_beg = clock(); // compute mean for row-wise Mat row_result(rows, cols, CV_32FC3); for (int row = 0; row < rows; ++row) { // initialize sums for row vector<float> sums(channels, 0.0f); int count = 0; for (int col = 0; col < radius; ++col) { if (col < cols) { count++; for (int k = 0; k < channels; ++k) { sums[k] += image.at<Vec3f>(row, col)[k]; } } } // process row for (int col = 0; col < cols; ++col) { int left = col - radius - 1; int right = col + radius; if (left >= 0) { count--; for (int k = 0; k < channels; ++k) { sums[k] -= image.at<Vec3f>(row, left)[k]; } } if (right < cols) { count++; for (int k = 0; k < channels; ++k) { sums[k] += image.at<Vec3f>(row, right)[k]; } } for (int k = 0; k < channels; ++k) { row_result.at<Vec3f>(row, col)[k] = sums[k] / static_cast<float>(count); } } } // compute mean for column-wise for (int col = 0; col < cols; ++col) { // initialize sums for column vector<float> sums(channels, 0.0f); int count = 0; for (int row = 0; row < radius; ++row) { if (row < rows) { count++; for (int k = 0; k < channels; ++k) { sums[k] += row_result.at<Vec3f>(row, col)[k]; } } } // process column for (int row = 0; row < rows; ++row) { int up = row - radius - 1; int down = row + radius; if (up >= 0) { count--; for (int k = 0; k < channels; ++k) { sums[k] -= row_result.at<Vec3f>(up, col)[k]; } } if (down < rows) { count++; for (int k = 0; k < channels; ++k) { sums[k] += row_result.at<Vec3f>(down, col)[k]; } } for (int k = 0; k < channels; ++k) { result.at<Vec3f>(row, col)[k] = sums[k] / static_cast<float>(count); } } } result = cv::max(cv::min(result, 1.0), 0.0); time_end = clock(); cout << "box-filter-3 time cost: " << time_end - time_beg << endl; return result; }
以上就是C++ OpenCV实现boxfilter方框滤波的方法详解的详细内容,更多关于C++ OpenCV boxfilter方框滤波的资料请关注脚本之家其它相关文章!