场景需求
当对数据进行处理时,可能会遇到一种问题:计算区间(掩膜)内的数据和计算区间(掩膜)外的数据之间,有个断层,区间外的数据默认均为0或者NaN值,而这些原本不处于计算区间的数据可能无意中对计算区间内的数据带来影响。
举个例子:当用某个尺寸比如3*3大小的模板进行均值滤波处理时,掩膜边缘的滤波模板中存在处于掩膜外的数据,这些数据会引入很大的误差;又或者,在进行FFT(傅里叶变换)时,掩膜外的数据通常置0处理,那么掩膜边缘的数据会有很大的突变,这些都会使频域的信息受到影响,在反傅里叶变换后,引入较大的边缘高频误差或吉布斯噪声等等。
为了解决上述问题,需要对掩膜外的数据进行一定的处理,比如让它们同掩膜内的数据一致,又或者通过最小二乘的方法进行数据拟合。本文提供了一种数据填补方法(边缘扩展法):先按照行再按照列,分别用最靠近的掩膜边缘元素,填补至该行或者列某一侧的掩膜外数据。这样操作能大大减少由数据突变引起的误差,属于一种比较简单的填补方法,实现起来很方便也便于理解。
功能函数代码
/** * @brief FillMaskData 掩膜外数据填补函数 * @param phasein 需要填补数据的数组 * @param mask 同尺寸的掩膜 * @return 填补后的数组 */ //掩膜外数据填充:为了避免掩膜边界的值受0值影响导致异常 cv::Mat FillMaskData(cv::Mat phasein,cv::Mat mask) { cv::Mat phase = phasein.clone(); /******************************************************************** * mask为0的区域,对phase进行行操作,用最靠近的元素替换掉其他元素 * 0 0 0 0 0 0 1 2 3 4 0 1 2 3 4 * 0 0 1 0 0 5 6 7 8 9 行操作后 7 7 7 7 7 * mask = 0 1 1 1 0 ,phase = 9 8 7 6 5 ========》phase = 8 8 7 6 6 * 0 0 1 0 0 4 3 2 1 0 2 2 2 2 2 * 0 0 0 0 0 1 2 3 4 5 1 2 3 4 5 ********************************************************************/ for (int i = 0; i != mask.rows; ++i) { int j = 0; int jj = mask.cols - 1; const uchar* p = mask.ptr<uchar>(i); for (; j != mask.cols; j++, p++) { if (*p != 0) break; } p = mask.ptr<uchar>(i) + jj; for (; jj != -1; jj--, p--) { if (*p != 0) break; } if (j > 0 && j < mask.cols) phase.row(i)(cv::Range::all(), cv::Range(0, j)).setTo(phase.at<float>(i, j)); if (jj > -1 && jj < mask.cols - 1) phase.row(i)(cv::Range::all(), cv::Range(jj + 1, mask.cols)).setTo(phase.at<float>(i, jj)); } /******************************************************************** * mask为0的区域,对phase进行列操作,用最靠近的元素替换掉其他元素, * 0 0 0 0 0 0 1 2 3 4 0 8 7 6 4 * 0 0 1 0 0 7 7 7 7 7 列操作后 7 8 7 6 7 * mask = 0 1 1 1 0 ,phase = 8 8 7 6 6 ========》phase = 8 8 7 6 6 * 0 0 1 0 0 2 2 2 2 2 2 8 2 6 2 * 0 0 0 0 0 1 2 3 4 5 1 8 2 6 5 ********************************************************************/ cv::Mat mask_t = mask.t(); //mask转置 for (int i = 0; i != mask_t.rows; ++i) { int j = 0; int jj = mask_t.cols - 1; const uchar* p = mask_t.ptr<uchar>(i); for (; j != mask_t.cols; j++, p++) { if (*p != 0) break; } p = mask_t.ptr<uchar>(i) + jj; for (; jj != -1; jj--, p--) { if (*p != 0) break; } if (j > 0 && j < mask_t.cols) phase.col(i)(cv::Range(0, j), cv::Range::all()).setTo(phase.at<float>(j, i)); if (jj > -1 && jj < mask_t.cols - 1) phase.col(i)(cv::Range(jj + 1, mask_t.cols), cv::Range::all()).setTo(phase.at<float>(jj, i)); } return phase; }
C++测试代码
#include<iostream> #include<opencv2/opencv.hpp> #include<ctime> using namespace std; using namespace cv; cv::Mat FillMaskData(cv::Mat phasein, cv::Mat mask); int main(void) { cv::Mat src = cv::Mat::zeros(100, 100, CV_32FC1); cv::Mat mask = cv::Mat::zeros(100, 100, CV_8UC1);; cv::circle(mask, cv::Point(50, 50), 40, 255, -1); // 随机生成数据 for (int i = 0; i < src.rows; ++i) { uchar *m = mask.ptr<uchar>(i); float *s = src.ptr<float>(i); for (int j = 0; j < src.cols; ++j) { if (m[j] == 255) s[j] = rand() % 255+20; } } // 因为该Mat是个float型的矩阵,除以255可以归一化数据,进而便于观察数据的灰度颜色 src = src / 255; cv::Mat result = FillMaskData(src, mask); system("pause"); return 0; } //掩膜外数据填充:为了避免掩膜边界的值受0值影响导致异常 cv::Mat FillMaskData(cv::Mat phasein,cv::Mat mask) { cv::Mat phase = phasein.clone(); /******************************************************************** * mask为0的区域,对phase进行行操作,用最靠近的元素替换掉其他元素 * 0 0 0 0 0 0 1 2 3 4 0 1 2 3 4 * 0 0 1 0 0 5 6 7 8 9 行操作后 7 7 7 7 7 * mask = 0 1 1 1 0 ,phase = 9 8 7 6 5 ========》phase = 8 8 7 6 6 * 0 0 1 0 0 4 3 2 1 0 2 2 2 2 2 * 0 0 0 0 0 1 2 3 4 5 1 2 3 4 5 ********************************************************************/ for (int i = 0; i != mask.rows; ++i) { int j = 0; int jj = mask.cols - 1; const uchar* p = mask.ptr<uchar>(i); for (; j != mask.cols; j++, p++) { if (*p != 0) break; } p = mask.ptr<uchar>(i) + jj; for (; jj != -1; jj--, p--) { if (*p != 0) break; } if (j > 0 && j < mask.cols) phase.row(i)(cv::Range::all(), cv::Range(0, j)).setTo(phase.at<float>(i, j)); if (jj > -1 && jj < mask.cols - 1) phase.row(i)(cv::Range::all(), cv::Range(jj + 1, mask.cols)).setTo(phase.at<float>(i, jj)); } /******************************************************************** * mask为0的区域,对phase进行列操作,用最靠近的元素替换掉其他元素, * 0 0 0 0 0 0 1 2 3 4 0 8 7 6 4 * 0 0 1 0 0 7 7 7 7 7 列操作后 7 8 7 6 7 * mask = 0 1 1 1 0 ,phase = 8 8 7 6 6 ========》phase = 8 8 7 6 6 * 0 0 1 0 0 2 2 2 2 2 2 8 2 6 2 * 0 0 0 0 0 1 2 3 4 5 1 8 2 6 5 ********************************************************************/ cv::Mat mask_t = mask.t(); //mask转置 for (int i = 0; i != mask_t.rows; ++i) { int j = 0; int jj = mask_t.cols - 1; const uchar* p = mask_t.ptr<uchar>(i); for (; j != mask_t.cols; j++, p++) { if (*p != 0) break; } p = mask_t.ptr<uchar>(i) + jj; for (; jj != -1; jj--, p--) { if (*p != 0) break; } if (j > 0 && j < mask_t.cols) phase.col(i)(cv::Range(0, j), cv::Range::all()).setTo(phase.at<float>(j, i)); if (jj > -1 && jj < mask_t.cols - 1) phase.col(i)(cv::Range(jj + 1, mask_t.cols), cv::Range::all()).setTo(phase.at<float>(jj, i)); } return phase; }
测试效果
图1 原图
图2 掩膜
图3 填补后效果图
图3 填补后效果图
填补后效果图虽然看起来很丑,但对数据处理而言还是很有益处的,四角的区域为黑色是因为没有对应的掩膜数据填充,也没什么影响,想改进下也是很简单的~
本文是基于OpenCV实现的,用数组等数据结构或者Eigen库之类的也可,逻辑懂了,这些都只是工具而已~
本文介绍的方法比较简单,大家有更好更高级的方法欢迎评论留言,一起学习进步!
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!