译自《The OpenCV Reference Manual Release 2.3》
CHAPTER THREE: IMGPROC. IMAGE PROCESSING 3.1 Image Filtering
本节描述对2D图像执行的各种线性和非线性的滤波操作。即用图像中每个像素点(x,y)临近的点进行运算。如果是线性滤波器,结果是每个像素值的加权和;如果是形态操作,结果是最小或最大值之类的。对每个坐标的像素操作的输出结果也在同一个坐标(x,y)处,这就意味着输出图像和输入图像有相同的大小。通常情况下,这些函数支持多通道图像,即对每个通道单独进行操作。因此,输出图像也与输入图像有相同的通道数。
本节描述的函数与类的另一个共同的特点是,不同于简单的算术运算,他们需要对一些不存在的像素值进行推测。例如,你想使用 3*3的高斯滤波器,处理图像每行最左侧的像素时还需要其左侧的像素,也就是图像外的像素。你可以让这些像素等于源图像最左侧的像素(“复制边(replicated border)”外推法),或者假设所有不存在的像素值为零(“恒量边(constant border)”外推法),等等。OpenCV允许你指定外推方法。详情请参阅 borderInterpolate()函数的功能及其参数描述。
class BaseColumnFilter { public: virtual ~BaseColumnFilter(); // 用以被用户重写 // // 对列的集合进行滤波操作 // 输入"dstcount + ksize - 1" 行,输出"dstcount" 行, // 输入和输出的每行含有"width"个元素, // 滤波之后的行写入缓存"dst"中. virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width) = 0; // 重置滤波器的状态(IIR滤波器中可能用到) virtual void reset(); int ksize; // 核的孔径 int anchor; // 定位点坐标 // 处理过程中一般不使用 };类 BaseColumnFilter是使用单列核对数据滤波的基础类。滤波不一定是线性滤波,表示如下:
class BaseFilter { public: virtual ~BaseFilter(); // 用以被用户重写 // // 对列的集合进行滤波操作 // 输入"dstcount + ksize.height - 1" 行,输出"dstcount" 行, // 输入的每行含有"(width + ksize.width-1)*cn"个元素 // 输出的每行含有"width*cn"个元素, // 滤波之后的行写入缓存"dst"中. virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width, int cn) = 0; // 重置滤波器的状态(IIR滤波器中可能用到) virtual void reset(); Size ksize; Point anchor; };类 BaseFilter 是使用2D核对数据滤波的基础类。滤波不一定是线性的,可以表示如下:
class BaseRowFilter { public: virtual ~BaseRowFilter(); // 用以被用户重写 // // 对输入的单列进行滤波操作 // 输入列有 "width"个元素, 每个元素有 "cn" 个通道. // 滤波之后的行写入缓存"dst"中. virtual void operator()(const uchar* src, uchar* dst, int width, int cn) = 0; int ksize, anchor; };类 BaseRowFilter 是使用单列核对数据滤波的基础类。滤波不一定是线性的,可以表示如下:
class FilterEngine { public: // 空的构造函数 FilterEngine(); // 构造2D的不可分的滤波器(!_filter2D.empty())或者 // 可分的滤波器 (!_rowFilter.empty() && !_columnFilter.empty()) // 输入数据类型为 "srcType", 输出类型为"dstType", // 中间的数据类型为 "bufType". // _rowBorderType 何 _columnBorderType 决定图像边界如何被外推扩充 // 只有 _rowBorderType and/or _columnBorderType // == BORDER_CONSTANT 时 _borderValue 才会被用到 FilterEngine(const Ptr<BaseFilter>& _filter2D, const Ptr<BaseRowFilter>& _rowFilter, const Ptr<BaseColumnFilter>& _columnFilter, int srcType, int dstType, int bufType, int _rowBorderType=BORDER_REPLICATE, int _columnBorderType=-1, // 默认使用 _rowBorderType const Scalar& _borderValue=Scalar()); virtual ~FilterEngine(); // 初始引擎的分割函数 void init(const Ptr<BaseFilter>& _filter2D, const Ptr<BaseRowFilter>& _rowFilter, const Ptr<BaseColumnFilter>& _columnFilter, int srcType, int dstType, int bufType, int _rowBorderType=BORDER_REPLICATE, int _columnBorderType=-1, const Scalar& _borderValue=Scalar()); // 定义图像尺寸"wholeSize"为ROI开始滤波. // 返回图像开始的y-position坐标. virtual int start(Size wholeSize, Rect roi, int maxBufRows=-1); // 另一种需要图像的开始 virtual int start(const Mat& src, const Rect& srcRoi=Rect(0,0,-1,-1), bool isolated=false, int maxBufRows=-1); // 处理源图像的另一部分 // 从"src"到"dst"处理"srcCount" 行 // 返回处理的行数 virtual int proceed(const uchar* src, int srcStep, int srcCount, uchar* dst, int dstStep); // 处理整个ROI的高层调用 virtual void apply( const Mat& src, Mat& dst, const Rect& srcRoi=Rect(0,0,-1,-1), Point dstOfs=Point(0,0), bool isolated=false); bool isSeparable() const { return filter2D.empty(); } // 输入图中未被处理的行数 int remainingInputRows() const; // 输入中未被处理的行数 int remainingOutputRows() const; // 源图的开始和结束行 int startY, endY; // 指向滤波器的指针 Ptr<BaseFilter> filter2D; Ptr<BaseRowFilter> rowFilter; Ptr<BaseColumnFilter> columnFilter; };类 FilterEngine 可以被用于对任何一个图像进行滤波。它包含了所有必要的缓冲区,计算需要的图像外的“虚”像素推算值等等。通过各种创建 *Filter 的函数(见下文)可以返回指向初始化的 FilterEngine 的实例,之后可以使用这些实例中的高层接口如 filter2D(), erode(), dilate() 等。因此,此类在OpenCV的很多滤波函数中起着关键的作用。
void laplace_f(const Mat& src, Mat& dst) { CV_Assert( src.type() == CV_32F ); dst.create(src.size(), src.type()); // get the derivative and smooth kernels for d2I/dx2. // for d2I/dy2 consider using the same kernels, just swapped Mat kd, ks; getSobelKernels( kd, ks, 2, 0, ksize, false, ktype ); // process 10 source rows at once int DELTA = std::min(10, src.rows); Ptr<FilterEngine> Fxx = createSeparableLinearFilter(src.type(), dst.type(), kd, ks, Point(-1,-1), 0, borderType, borderType, Scalar() ); Ptr<FilterEngine> Fyy = createSeparableLinearFilter(src.type(), dst.type(), ks, kd, Point(-1,-1), 0, borderType, borderType, Scalar() ); int y = Fxx->start(src), dsty = 0, dy = 0; Fyy->start(src); const uchar* sptr = src.data + y*src.step; // allocate the buffers for the spatial image derivatives; // the buffers need to have more than DELTA rows, because at the // last iteration the output may take max(kd.rows-1,ks.rows-1) // rows more than the input. Mat Ixx( DELTA + kd.rows - 1, src.cols, dst.type() ); Mat Iyy( DELTA + kd.rows - 1, src.cols, dst.type() ); // inside the loop always pass DELTA rows to the filter // (note that the "proceed" method takes care of possibe overflow, since // it was given the actual image height in the "start" method) // on output you can get: // * < DELTA rows (initial buffer accumulation stage) // * = DELTA rows (settled state in the middle) // * > DELTA rows (when the input image is over, generate // "virtual" rows using the border mode and filter them) // this variable number of output rows is dy. // dsty is the current output row. // sptr is the pointer to the first input row in the portion to process for( ; dsty < dst.rows; sptr += DELTA*src.step, dsty += dy ) { Fxx->proceed( sptr, (int)src.step, DELTA, Ixx.data, (int)Ixx.step ); dy = Fyy->proceed( sptr, (int)src.step, DELTA, d2y.data, (int)Iyy.step ); if( dy > 0 ) { Mat dstripe = dst.rowRange(dsty, dsty + dy); add(Ixx.rowRange(0, dy), Iyy.rowRange(0, dy), dstripe); } } }如果你不需要对滤波过程的控制,你可以简单地使用 FilterEngine:: apply方法。
void FilterEngine::apply(const Mat& src, Mat& dst, const Rect& srcRoi, Point dstOfs, bool isolated) { // check matrix types CV_Assert( src.type() == srcType && dst.type() == dstType ); // handle the "whole image" case Rect _srcRoi = srcRoi; if( _srcRoi == Rect(0,0,-1,-1) ) _srcRoi = Rect(0,0,src.cols,src.rows); // check if the destination ROI is inside dst. // and FilterEngine::start will check if the source ROI is inside src. CV_Assert( dstOfs.x >= 0 && dstOfs.y >= 0 && dstOfs.x + _srcRoi.width <= dst.cols && dstOfs.y + _srcRoi.height <= dst.rows ); // start filtering int y = start(src, _srcRoi, isolated); // process the whole ROI. Note that "endY - startY" is the total number // of the source rows to process // (including the possible rows outside of srcRoi but inside the source image) proceed( src.data + y*src.step, (int)src.step, endY - startY, dst.data + dstOfs.y*dst.step + dstOfs.x*dst.elemSize(), (int)dst.step ); }不同于OpenCV的早期版本,现在的滤波操作支持图像ROI概念,也就是说,在ROI图像之外但在图像之内的像素点可以用于滤波操作。例如,你可以取单个像素作为ROI滤波。通过滤波器之后将范围特定的像素。然而,通过传递 FilterEngine::start或 FilterEngine::apply 参数 isolated=false 它有可能仍是旧的图像。你可以明确指定传递ROI给 FilterEngine::apply 函数或者构造新的矩阵头:
// compute dI/dx derivative at src(x,y) // method 1: // form a matrix header for a single value float val1 = 0; Mat dst1(1,1,CV_32F,&val1); Ptr<FilterEngine> Fx = createDerivFilter(CV_32F, CV_32F, 1, 0, 3, BORDER_REFLECT_101); Fx->apply(src, Rect(x,y,1,1), Point(), dst1); // method 2: // form a matrix header for a single value float val2 = 0; Mat dst2(1,1,CV_32F,&val2); Mat pix_roi(src, Rect(x,y,1,1)); Sobel(pix_roi, dst2, dst2.type(), 1, 0, 3, 1, 0, BORDER_REFLECT_101);探索中的数据类型。由于它是在 BaseFilter 描述中提到的具体的滤波器,虽然 Base*Filter::operator() 除了UCHAR的指针并其他类型的信息, 但实际它可以处理任何类型的数据。为了保证所有的函数可以运行,使用以下规则: