-
前言
到今天为止,已经接触图像处理行业四年左右,但是大部分时间都是在调用别人已经封装好的函数,即传说中的掉包侠。虽然清楚算法原理,但是自己从来没有比较系统的实现过一个算法。今天就以均值滤波算法为例,用C++自行实现。均值滤波算法的原理比较简单,这里就不再赘述。
-
最简单的均值滤波算法实现
效果图
运行时间对比
代码:
//////////////////////////////////
//opencv4.1.0
//////////////////////////////////
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void MeanImage(Mat& src, Mat& dst); //均值滤波
int main() {
TickMeter tm;
Mat src, srcOpencv, srcMyself;
src = imread("1.png",0);
//调用opencv的函数实现均值滤波
tm.start();
blur(src, srcOpencv, Size(3, 3));
tm.stop();
cout << "opencv函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//使用c++自行实现均值滤波
tm.start();
MeanImage(src, srcMyself);
tm.stop();
cout << "自己实现的函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//显示
imshow("原图", src);
imshow("opencv函数均值滤波", srcOpencv);
imshow("自己实现的函数均值滤波", srcMyself);
waitKey(0);
return 0;
}
void MeanImage(Mat& src,Mat& dst) {
dst = Mat::zeros(src.size(), src.type());
for (size_t i = 1; i < src.rows-1; i++){
for (size_t j = 1; j < src.cols-1; j++){
dst.at<uchar>(i, j) = (src.at<uchar>(i - 1, j - 1) + src.at<uchar>(i - 1, j) + src.at<uchar>(i - 1, j + 1) +
src.at<uchar>(i, j - 1) + src.at<uchar>(i, j) + src.at<uchar>(i, j + 1) +
src.at<uchar>(i + 1, j - 1) + src.at<uchar>(i + 1, j) + src.at<uchar>(i + 1, j + 1)) / 9;
}
}
}
上面的代码虽然实现了均值滤波的功能,但是我们需要解决代码中存在的如下诸多缺陷:
1.掩模尺寸不能改变,只能实现掩模尺寸为3x3的滤波
2.没有对图像边界的像素进行处理
3.算法没有经过优化,因此运行时间太长
-
添加掩模尺寸可变的功能
效果图同上,这里就不再给出。但值得注意的一点是,到目前为止的代码都没有考虑边界像素的处理问题,因此结果图中宽度为(Height-1)/2的边界像素值都为0 ,其中Height为掩模的高度(掩模的宽高相等)。
边界像素值为0
代码:
//////////////////////////////////
//opencv4.1.0
//////////////////////////////////
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void MeanImage(Mat& src, Mat& dst, Size ksize); //均值滤波
int main() {
TickMeter tm;
Mat src, srcOpencv, srcMyself;
src = imread("1.png",0);
//调用opencv的函数实现均值滤波
tm.start();
blur(src, srcOpencv, Size(3, 3));
tm.stop();
cout << "opencv函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//使用c++自行实现均值滤波
tm.start();
MeanImage(src, srcMyself,Size(3,3));
tm.stop();
cout << "自己实现的函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//显示
imshow("原图", src);
imshow("opencv函数均值滤波", srcOpencv);
imshow("自己实现的函数均值滤波", srcMyself);
waitKey(0);
return 0;
}
void MeanImage(Mat& src,Mat& dst,Size ksize) {
//初始化输出图像
dst = Mat::zeros(src.size(), src.type());
//确保掩模宽高相等
if (ksize.height != ksize.width){
ksize.height = ksize.width;
}
//确保掩模宽高为奇数
if (ksize.height % 2 ==0){
ksize.height = ksize.height + 1;
ksize.width = ksize.width + 1;
}
//掩模宽高的一半
int HH = (ksize.height - 1) / 2;
int HW = (ksize.width - 1) / 2;
//遍历除边界像素外的所有像素
int sum = 0;
for (size_t r = HH; r < src.rows - HH; r++){
for (size_t c = HW; c < src.cols - HW; c++){
//计算掩模覆盖的所有像素值的和
for (size_t i = r-HH; i <= r+HH; i++){
for (size_t j = c-HW; j <= c+HW; j++){
sum += src.at<uchar>(i,j);
}
}
//求均值并赋值给相应位置的像素
dst.at<uchar>(r,c) = sum / ksize.area();
sum = 0;
}
}
}
-
添加对图像边界像素的处理
可直接调用opencv中的copyMakeBorder函数对图像边界像素进行扩充,此函数本文不做过多介绍,详情可参考张美丽的博客。对边界扩充后的效果图:略
代码:
//////////////////////////////////
//opencv4.1.0
//////////////////////////////////
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void MeanImage(Mat& src, Mat& dst, Size ksize); //均值滤波
int main() {
TickMeter tm;
Mat src, srcOpencv, srcMyself;
src = imread("1.png",0);
//调用opencv的函数实现均值滤波
tm.start();
blur(src, srcOpencv, Size(3, 3));
tm.stop();
cout << "opencv函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//使用c++自行实现均值滤波
tm.start();
MeanImage(src, srcMyself,Size(3,3));
tm.stop();
cout << "自己实现的函数运行时间:" << tm.getTimeMilli() << "ms" << endl;
//显示
imshow("原图", src);
imshow("opencv函数均值滤波", srcOpencv);
imshow("自己实现的函数均值滤波", srcMyself);
waitKey(0);
return 0;
}
void MeanImage(Mat& src,Mat& dst,Size ksize) {
//初始化输出图像
dst = Mat::zeros(src.size(), src.type());
//确保掩模宽高相等
if (ksize.height != ksize.width){
ksize.height = ksize.width;
}
//确保掩模宽高为奇数
if (ksize.height % 2 ==0){
ksize.height = ksize.height + 1;
ksize.width = ksize.width + 1;
}
//掩模宽高的一半
int HH = (ksize.height - 1) / 2;
int HW = (ksize.width - 1) / 2;
//扩充边缘像素
Mat MakeBorder;
copyMakeBorder(src, MakeBorder, HH, HH, HW, HW, BORDER_REFLECT_101);
//遍历除边界像素外的所有像素
int sum = 0;
for (size_t r = HH; r < src.rows + HH; r++){
for (size_t c = HW; c < src.cols + HW; c++){
//计算掩模覆盖的所有像素值的和
for (size_t i = r-HH; i <= r+HH; i++){
for (size_t j = c-HW; j <= c+HW; j++){
sum += MakeBorder.at<uchar>(i,j);
}
}
//求均值并赋值给相应位置的像素
dst.at<uchar>(r-HH,c-HW) = sum / ksize.area();
sum = 0;
}
}
}
-
算法优化的内容较多,由于篇幅的问题,这里就不在详述。详情请查看本人的另一篇博客优化均值滤波算法