目录
一,Mat基础数据结构
1,Mat的数据成员
2,flags
(1)深度 depth()
(2)通道数 channels()
(3)图像类型 type()
(4)flag第13-14位
(5)判断连续 isContinuous()
(6)子图标志 isSubmatrix()
(7)magic signature
3,UMatData
4,step
二,Mat常用函数
1,Mat类的create函数
2,Mat类的copyTo函数
3,Mat类的=运算符
4,图像截取 Mat(const Mat&, const Rect&)
5,imwrite
三,其他基础数据结构
1,图像尺寸上限
2,Size
3,***Array
(1)InputArray
(2)OutputArray
(3)InputOutputArray
四,相位相关法 phaseCorrelate
1,phaseCorrelate
2,汉宁窗
五,直方图均衡
1,直方图统计
2,灰度变换
3,直方图均衡
六,可分离滤波器
1,可分离滤波器的工厂
2,ocvSepFilter、sepFilter2D
3,Sobel
int flags;
//! the matrix dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! helper fields used in locateROI and adjustROI
const uchar* datastart;
const uchar* dataend;
const uchar* datalimit;
//! custom allocator
MatAllocator* allocator;
UMatData* u;
MatSize size;
MatStep step;
其中flags、u指针、step在下面的章节。
成员dims是维数,当维数是2时,成员rows和cols才有意义。
data是图像的数据指针。
以下宏来自opencv-4.2.0\modules\gapi\include\opencv2\gapi\own\cvdefs.hpp中的源代码。
按照从低到高位分别是:
#define CV_CN_SHIFT 3
#define CV_DEPTH_MAX (1 << CV_CN_SHIFT)
#define CV_8U 0
#define CV_8S 1
#define CV_16U 2
#define CV_16S 3
#define CV_32S 4
#define CV_32F 5
#define CV_64F 6
#define CV_16F 7
#define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK)
即flags的前3位存的是8种深度。
后缀表示数据类型,U unsigned S signed F float
inline
int Mat::depth() const
{
return CV_MAT_DEPTH(flags);
}
depth函数用来获取深度。
#define CV_CN_MAX 512
#define CV_CN_SHIFT 3
#define CV_MAT_CN_MASK ((CV_CN_MAX - 1) << CV_CN_SHIFT)
#define CV_MAT_CN(flags) ((((flags) & CV_MAT_CN_MASK) >> CV_CN_SHIFT) + 1)
最少1通道,最多513个通道,即flag的前3位是深度,接下来9位是通道数。
inline
int Mat::channels() const
{
return CV_MAT_CN(flags);
}
channels函数用来获取通道数。
CV_8U和CV_8UC1都等于0
#define CV_MAT_TYPE_MASK (CV_DEPTH_MAX*CV_CN_MAX - 1)
#define CV_MAT_TYPE(flags) ((flags) & CV_MAT_TYPE_MASK)
flag的前12位是type,由深度和通道数组合而成。
type() == (channels()-1) * depth()
暂无用途
#define CV_MAT_CONT_FLAG_SHIFT 14
#define CV_MAT_CONT_FLAG (1 << CV_MAT_CONT_FLAG_SHIFT)
#define CV_IS_MAT_CONT(flags) ((flags) & CV_MAT_CONT_FLAG)
#define CV_IS_CONT_MAT CV_IS_MAT_CONT
即flag的第15位,判断整个mat所有像素是否是连续存储。
inline
bool Mat::isContinuous() const
{
return (flags & CONTINUOUS_FLAG) != 0;
}
#define CV_SUBMAT_FLAG_SHIFT 15
#define CV_SUBMAT_FLAG (1 << CV_SUBMAT_FLAG_SHIFT)
#define CV_IS_SUBMAT(flags) ((flags) & CV_MAT_SUBMAT_FLAG)
CV_MAT_SUBMAT_FLAG找不到定义,应该就是CV_SUBMAT_FLAG
flag的第16位,判断图像是不是另外一个图像的子图。
SUBMATRIX_FLAG = CV_SUBMAT_FLAG
inline
bool Mat::isSubmatrix() const
{
return (flags & SUBMATRIX_FLAG) != 0;
}
flags的高16位是magic signature,用来区分Mat的类型
Mat对象包含了一个UMatData的结构体指针:UMatData* u;
struct CV_EXPORTS UMatData
{
enum MemoryFlag { COPY_ON_MAP=1, HOST_COPY_OBSOLETE=2,
DEVICE_COPY_OBSOLETE=4, TEMP_UMAT=8, TEMP_COPIED_UMAT=24,
USER_ALLOCATED=32, DEVICE_MEM_MAPPED=64,
ASYNC_CLEANUP=128
};
UMatData(const MatAllocator* allocator);
~UMatData();
// provide atomic access to the structure
void lock();
void unlock();
bool hostCopyObsolete() const;
bool deviceCopyObsolete() const;
bool deviceMemMapped() const;
bool copyOnMap() const;
bool tempUMat() const;
bool tempCopiedUMat() const;
void markHostCopyObsolete(bool flag);
void markDeviceCopyObsolete(bool flag);
void markDeviceMemMapped(bool flag);
const MatAllocator* prevAllocator;
const MatAllocator* currAllocator;
int urefcount;
int refcount;
uchar* data;
uchar* origdata;
size_t size;
UMatData::MemoryFlag flags;
void* handle;
void* userdata;
int allocatorFlags_;
int mapcount;
UMatData* originalUMatData;
};
不同的Mat对象共享一个内存块时,u指针是同一个值,而u中的refcount是引用计数。
step是关于内存分布的记录值。
struct CV_EXPORTS MatStep
{
MatStep();
explicit MatStep(size_t s);
const size_t& operator[](int i) const;
size_t& operator[](int i);
operator size_t() const;
MatStep& operator = (size_t s);
size_t* p;
size_t buf[2];
protected:
MatStep& operator = (const MatStep&);
};
p指针其实是个数组,其中记录着每一维度的内存地址间距。
如二维图像p->{100,1},则2行的间距是100字节,行内2个元素的间距是1字节。
MatStep重载了[],所以常用调用方式是:
Mat img;
cout << img.step[0];
opencv-4.2.0\modules\core\src\matrix.cpp中的create函数:
void Mat::create(int d, const int* _sizes, int _type)
{
int i;
CV_Assert(0 <= d && d <= CV_MAX_DIM && _sizes);
_type = CV_MAT_TYPE(_type);
if( data && (d == dims || (d == 1 && dims <= 2)) && _type == type() )
{
if( d == 2 && rows == _sizes[0] && cols == _sizes[1] )
return;
for( i = 0; i < d; i++ )
if( size[i] != _sizes[i] )
break;
if( i == d && (d > 1 || size[1] == 1))
return;
}
int _sizes_backup[CV_MAX_DIM]; // #5991
if (_sizes == (this->size.p))
{
for(i = 0; i < d; i++ )
_sizes_backup[i] = _sizes[i];
_sizes = _sizes_backup;
}
release();
if( d == 0 )
return;
flags = (_type & CV_MAT_TYPE_MASK) | MAGIC_VAL;
setSize(*this, d, _sizes, 0, true);
if( total() > 0 )
{
MatAllocator *a = allocator, *a0 = getDefaultAllocator();
#ifdef HAVE_TGPU
if( !a || a == tegra::getAllocator() )
a = tegra::getAllocator(d, _sizes, _type);
#endif
if(!a)
a = a0;
try
{
u = a->allocate(dims, size, _type, 0, step.p, ACCESS_RW /* ignored */, USAGE_DEFAULT);
CV_Assert(u != 0);
}
catch (...)
{
if (a == a0)
throw;
u = a0->allocate(dims, size, _type, 0, step.p, ACCESS_RW /* ignored */, USAGE_DEFAULT);
CV_Assert(u != 0);
}
CV_Assert( step[dims-1] == (size_t)CV_ELEM_SIZE(flags) );
}
addref();
finalizeHdr(*this);
}
void Mat::create(const std::vector& _sizes, int _type)
{
create((int)_sizes.size(), _sizes.data(), _type);
}
第一个函数入参_sizes是一个数组,常见的是2个数,即{_rows, _cols},函数会调用allocate函数来分配内存。
第二个函数是个重载,传入的是vector而不是数组。
opencv-4.2.0\modules\core\include\opencv2\core\mat.inl.hpp 中的create函数:
inline
void Mat::create(int _rows, int _cols, int _type)
{
_type &= TYPE_MASK;
if( dims <= 2 && rows == _rows && cols == _cols && type() == _type && data )
return;
int sz[] = {_rows, _cols};
create(2, sz, _type);
}
inline
void Mat::create(Size _sz, int _type)
{
create(_sz.height, _sz.width, _type);
}
第一个函数是调用上面的函数。
第二个函数是调用第一个函数。
opencv-4.2.0\modules\core\src\copy.cpp里面的源代码:
/* dst = src */
void Mat::copyTo( OutputArray _dst ) const
{
CV_INSTRUMENT_REGION();
#ifdef HAVE_CUDA
if (_dst.isGpuMat())
{
_dst.getGpuMat().upload(*this);
return;
}
#endif
int dtype = _dst.type();
if( _dst.fixedType() && dtype != type() )
{
CV_Assert( channels() == CV_MAT_CN(dtype) );
convertTo( _dst, dtype );
return;
}
if( empty() )
{
_dst.release();
return;
}
if( _dst.isUMat() )
{
_dst.create( dims, size.p, type() );
UMat dst = _dst.getUMat();
CV_Assert(dst.u != NULL);
size_t i, sz[CV_MAX_DIM] = {0}, dstofs[CV_MAX_DIM], esz = elemSize();
CV_Assert(dims > 0 && dims < CV_MAX_DIM);
for( i = 0; i < (size_t)dims; i++ )
sz[i] = size.p[i];
sz[dims-1] *= esz;
dst.ndoffset(dstofs);
dstofs[dims-1] *= esz;
dst.u->currAllocator->upload(dst.u, data, dims, sz, dstofs, dst.step.p, step.p);
return;
}
if( dims <= 2 )
{
_dst.create( rows, cols, type() );
Mat dst = _dst.getMat();
if( data == dst.data )
return;
if( rows > 0 && cols > 0 )
{
Mat src = *this;
Size sz = getContinuousSize2D(src, dst, (int)elemSize());
CV_CheckGE(sz.width, 0, "");
const uchar* sptr = src.data;
uchar* dptr = dst.data;
#if IPP_VERSION_X100 >= 201700
CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippiCopy_8u_C1R_L, sptr, (int)src.step, dptr, (int)dst.step, ippiSizeL(sz.width, sz.height)) >= 0)
#endif
for (; sz.height--; sptr += src.step, dptr += dst.step)
memcpy(dptr, sptr, sz.width);
}
return;
}
_dst.create( dims, size, type() );
Mat dst = _dst.getMat();
if( data == dst.data )
return;
if( total() != 0 )
{
const Mat* arrays[] = { this, &dst };
uchar* ptrs[2] = {};
NAryMatIterator it(arrays, ptrs, 2);
size_t sz = it.size*elemSize();
for( size_t i = 0; i < it.nplanes; i++, ++it )
memcpy(ptrs[1], ptrs[0], sz);
}
}
大概扫了一眼,主要是调出参的create函数,然后用memcpy做深拷贝。
opencv-4.2.0\modules\core\include\opencv2\core\mat.inl.hpp里面的源代码:
inline
Mat& Mat::operator = (const Mat& m)
{
if( this != &m )
{
if( m.u )
CV_XADD(&m.u->refcount, 1);
release();
flags = m.flags;
if( dims <= 2 && m.dims <= 2 )
{
dims = m.dims;
rows = m.rows;
cols = m.cols;
step[0] = m.step[0];
step[1] = m.step[1];
}
else
copySize(m);
data = m.data;
datastart = m.datastart;
dataend = m.dataend;
datalimit = m.datalimit;
allocator = m.allocator;
u = m.u;
}
return *this;
}
其中最核心的一句:
data = m.data;
直接把data指针拷贝过来,不拷贝数据。
opencv\opencv-4.2.0\modules\core\src\matrix.cpp里面的源代码:
Mat::Mat(const Mat& m, const Rect& roi)
: flags(m.flags), dims(2), rows(roi.height), cols(roi.width),
data(m.data + roi.y*m.step[0]),
datastart(m.datastart), dataend(m.dataend), datalimit(m.datalimit),
allocator(m.allocator), u(m.u), size(&rows)
{
CV_Assert( m.dims <= 2 );
size_t esz = CV_ELEM_SIZE(flags);
data += roi.x*esz;
CV_Assert( 0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols &&
0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows );
if( u )
CV_XADD(&u->refcount, 1);
if( roi.width < m.cols || roi.height < m.rows )
flags |= SUBMATRIX_FLAG;
step[0] = m.step[0]; step[1] = esz;
updateContinuityFlag();
if( rows <= 0 || cols <= 0 )
{
release();
rows = cols = 0;
}
}
只进行指针运算,没有深拷贝操作,所以几乎不耗时。参考Mat的内存结构
截取对象的u指针和原对象的u指针是一样的,所以他们是对同一块内存进行引用计数。
opencv-4.2.0\modules\imgcodecs\src\loadsave.cpp里面的源代码:
static const size_t CV_IO_MAX_IMAGE_PARAMS = cv::utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PARAMS", 50);
static bool imwrite_( const String& filename, const std::vector& img_vec,
const std::vector& params, bool flipv )
{
bool isMultiImg = img_vec.size() > 1;
std::vector write_vec;
ImageEncoder encoder = findEncoder( filename );
if( !encoder )
CV_Error( Error::StsError, "could not find a writer for the specified extension" );
for (size_t page = 0; page < img_vec.size(); page++)
{
Mat image = img_vec[page];
CV_Assert(!image.empty());
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 );
Mat temp;
if( !encoder->isFormatSupported(image.depth()) )
{
CV_Assert( encoder->isFormatSupported(CV_8U) );
image.convertTo( temp, CV_8U );
image = temp;
}
if( flipv )
{
flip(image, temp, 0);
image = temp;
}
write_vec.push_back(image);
}
encoder->setDestination( filename );
CV_Assert(params.size() <= CV_IO_MAX_IMAGE_PARAMS*2);
bool code = false;
try
{
if (!isMultiImg)
code = encoder->write( write_vec[0], params );
else
code = encoder->writemulti( write_vec, params ); //to be implemented
}
catch (const cv::Exception& e)
{
std::cerr << "imwrite_('" << filename << "'): can't write data: " << e.what() << std::endl << std::flush;
}
catch (...)
{
std::cerr << "imwrite_('" << filename << "'): can't write data: unknown exception" << std::endl << std::flush;
}
// CV_Assert( code );
return code;
}
bool imwrite( const String& filename, InputArray _img,
const std::vector& params )
{
CV_TRACE_FUNCTION();
CV_Assert(!_img.empty());
std::vector img_vec;
if (_img.isMatVector() || _img.isUMatVector())
_img.getMatVector(img_vec);
else
img_vec.push_back(_img.getMat());
CV_Assert(!img_vec.empty());
return imwrite_(filename, img_vec, params, false);
}
imwrite函数的第三个参数不太常用,是个vector参数列表,里面不能超过100个元素。
opencv-4.2.0\modules\imgcodecs\src\loadsave.cpp里面的源代码:
static const size_t CV_IO_MAX_IMAGE_WIDTH = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_WIDTH", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_HEIGHT = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_HEIGHT", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30);
宽高都不超过100万,且像素总数不超过10亿
尺寸校验函数:
static Size validateInputImageSize(const Size& size)
{
CV_Assert(size.width > 0);
CV_Assert(static_cast(size.width) <= CV_IO_MAX_IMAGE_WIDTH);
CV_Assert(size.height > 0);
CV_Assert(static_cast(size.height) <= CV_IO_MAX_IMAGE_HEIGHT);
uint64 pixels = (uint64)size.width * (uint64)size.height;
CV_Assert(pixels <= CV_IO_MAX_IMAGE_PIXELS);
return size;
}
typedef Size_ Size2i;
typedef Size_ Size2l;
typedef Size_ Size2f;
typedef Size_ Size2d;
typedef Size2i Size;
Size_是个模板类,只有width和height2个数据成员。
modules\core\include\opencv2\core\mat.hpp
typedef const _InputArray& InputArray;
typedef InputArray InputArrayOfArrays;
_InputArray类有3个数据成员:
public:
template _InputArray(const Mat_<_Tp>& m);
Mat getMat(int idx=-1) const;
protected:
int flags;
void* obj;
Size sz;
void init(int _flags, const void* _obj);
void init(int _flags, const void* _obj, Size _sz);
obj指针用来指向图像。
构造函数很简单,直接把Mat对象强转成void指针:
inline _InputArray::_InputArray(const Mat& m) { init(MAT+ACCESS_READ, &m); }
inline Mat _InputArray::getMat(int i) const
{
if( kind() == MAT && i < 0 )
return *(const Mat*)obj;
return getMat_(i);
}
getMat是把obj强转回Mat对象。
typedef const _OutputArray& OutputArray;
typedef OutputArray OutputArrayOfArrays;
_OutputArray类继承了_InputArray类,没有新增数据成员。
功能是类似的:
inline _OutputArray::_OutputArray(Mat& m) { init(MAT+ACCESS_WRITE, &m); }
typedef const _InputOutputArray& InputOutputArray;
typedef InputOutputArray InputOutputArrayOfArrays;
_InputOutputArray类继承了_OutputArray类,没有新增数据成员。
功能是类似的:
inline _InputOutputArray::_InputOutputArray(Mat& m) { init(MAT+ACCESS_RW, &m); }
phaseCorrelate函数是利用相位相关法,给两张图片做频域配准。
modules\imgproc\src\phasecorr.cpp
cv::Point2d cv::phaseCorrelate(InputArray _src1, InputArray _src2, InputArray _window, double* response)
{
CV_INSTRUMENT_REGION();
Mat src1 = _src1.getMat();
Mat src2 = _src2.getMat();
Mat window = _window.getMat();
CV_Assert( src1.type() == src2.type());
CV_Assert( src1.type() == CV_32FC1 || src1.type() == CV_64FC1 );
CV_Assert( src1.size == src2.size);
if(!window.empty())
{
CV_Assert( src1.type() == window.type());
CV_Assert( src1.size == window.size);
}
int M = getOptimalDFTSize(src1.rows);
int N = getOptimalDFTSize(src1.cols);
Mat padded1, padded2, paddedWin;
if(M != src1.rows || N != src1.cols)
{
copyMakeBorder(src1, padded1, 0, M - src1.rows, 0, N - src1.cols, BORDER_CONSTANT, Scalar::all(0));
copyMakeBorder(src2, padded2, 0, M - src2.rows, 0, N - src2.cols, BORDER_CONSTANT, Scalar::all(0));
if(!window.empty())
{
copyMakeBorder(window, paddedWin, 0, M - window.rows, 0, N - window.cols, BORDER_CONSTANT, Scalar::all(0));
}
}
else
{
padded1 = src1;
padded2 = src2;
paddedWin = window;
}
Mat FFT1, FFT2, P, Pm, C;
// perform window multiplication if available
if(!paddedWin.empty())
{
// apply window to both images before proceeding...
multiply(paddedWin, padded1, padded1);
multiply(paddedWin, padded2, padded2);
}
// execute phase correlation equation
// Reference: http://en.wikipedia.org/wiki/Phase_correlation
dft(padded1, FFT1, DFT_REAL_OUTPUT);
dft(padded2, FFT2, DFT_REAL_OUTPUT);
mulSpectrums(FFT1, FFT2, P, 0, true);
magSpectrums(P, Pm);
divSpectrums(P, Pm, C, 0, false); // FF* / |FF*| (phase correlation equation completed here...)
idft(C, C); // gives us the nice peak shift location...
fftShift(C); // shift the energy to the center of the frame.
// locate the highest peak
Point peakLoc;
minMaxLoc(C, NULL, NULL, NULL, &peakLoc);
// get the phase shift with sub-pixel accuracy, 5x5 window seems about right here...
Point2d t;
t = weightedCentroid(C, peakLoc, Size(5, 5), response);
// max response is M*N (not exactly, might be slightly larger due to rounding errors)
if(response)
*response /= M*N;
// adjust shift relative to image center...
Point2d center((double)padded1.cols / 2.0, (double)padded1.rows / 2.0);
return (center - t);
}
前两个参数是传2张图片,第三个是应用窗函数去除图像的边界效应,文档中推荐使用汉宁窗。
void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type)
{
CV_INSTRUMENT_REGION();
CV_Assert( type == CV_32FC1 || type == CV_64FC1 );
CV_Assert( winSize.width > 1 && winSize.height > 1 );
_dst.create(winSize, type);
Mat dst = _dst.getMat();
int rows = dst.rows, cols = dst.cols;
AutoBuffer _wc(cols);
double* const wc = _wc.data();
double coeff0 = 2.0 * CV_PI / (double)(cols - 1), coeff1 = 2.0f * CV_PI / (double)(rows - 1);
for(int j = 0; j < cols; j++)
wc[j] = 0.5 * (1.0 - cos(coeff0 * j));
if(dst.depth() == CV_32F)
{
for(int i = 0; i < rows; i++)
{
float* dstData = dst.ptr(i);
double wr = 0.5 * (1.0 - cos(coeff1 * i));
for(int j = 0; j < cols; j++)
dstData[j] = (float)(wr * wc[j]);
}
}
else
{
for(int i = 0; i < rows; i++)
{
double* dstData = dst.ptr(i);
double wr = 0.5 * (1.0 - cos(coeff1 * i));
for(int j = 0; j < cols; j++)
dstData[j] = wr * wc[j];
}
}
// perform batch sqrt for SSE performance gains
cv::sqrt(dst, dst);
}
opencv-4.2.0\modules\imgproc\src\histogram.cpp 中的代码:
class EqualizeHistCalcHist_Invoker : public cv::ParallelLoopBody
{
public:
enum {HIST_SZ = 256};
EqualizeHistCalcHist_Invoker(cv::Mat& src, int* histogram, cv::Mutex* histogramLock)
: src_(src), globalHistogram_(histogram), histogramLock_(histogramLock)
{ }
void operator()( const cv::Range& rowRange ) const CV_OVERRIDE
{
int localHistogram[HIST_SZ] = {0, };
const size_t sstep = src_.step;
int width = src_.cols;
int height = rowRange.end - rowRange.start;
if (src_.isContinuous())
{
width *= height;
height = 1;
}
for (const uchar* ptr = src_.ptr(rowRange.start); height--; ptr += sstep)
{
int x = 0;
for (; x <= width - 4; x += 4)
{
int t0 = ptr[x], t1 = ptr[x+1];
localHistogram[t0]++; localHistogram[t1]++;
t0 = ptr[x+2]; t1 = ptr[x+3];
localHistogram[t0]++; localHistogram[t1]++;
}
for (; x < width; ++x)
localHistogram[ptr[x]]++;
}
cv::AutoLock lock(*histogramLock_);
for( int i = 0; i < HIST_SZ; i++ )
globalHistogram_[i] += localHistogram[i];
}
static bool isWorthParallel( const cv::Mat& src )
{
return ( src.total() >= 640*480 );
}
private:
EqualizeHistCalcHist_Invoker& operator=(const EqualizeHistCalcHist_Invoker&);
cv::Mat& src_;
int* globalHistogram_;
cv::Mutex* histogramLock_;
};
类继承了ParallelLoopBody,可以做并行加速。
灰度级HIST_SZ = 256
构造函数保存三个参数。
仿函数是统计直方图。
isWorthParallel函数是判断是否启用并行加速。
class EqualizeHistLut_Invoker : public cv::ParallelLoopBody
{
public:
EqualizeHistLut_Invoker( cv::Mat& src, cv::Mat& dst, int* lut )
: src_(src),
dst_(dst),
lut_(lut)
{ }
void operator()( const cv::Range& rowRange ) const CV_OVERRIDE
{
const size_t sstep = src_.step;
const size_t dstep = dst_.step;
int width = src_.cols;
int height = rowRange.end - rowRange.start;
int* lut = lut_;
if (src_.isContinuous() && dst_.isContinuous())
{
width *= height;
height = 1;
}
const uchar* sptr = src_.ptr(rowRange.start);
uchar* dptr = dst_.ptr(rowRange.start);
for (; height--; sptr += sstep, dptr += dstep)
{
int x = 0;
for (; x <= width - 4; x += 4)
{
int v0 = sptr[x];
int v1 = sptr[x+1];
int x0 = lut[v0];
int x1 = lut[v1];
dptr[x] = (uchar)x0;
dptr[x+1] = (uchar)x1;
v0 = sptr[x+2];
v1 = sptr[x+3];
x0 = lut[v0];
x1 = lut[v1];
dptr[x+2] = (uchar)x0;
dptr[x+3] = (uchar)x1;
}
for (; x < width; ++x)
dptr[x] = (uchar)lut[sptr[x]];
}
}
static bool isWorthParallel( const cv::Mat& src )
{
return ( src.total() >= 640*480 );
}
private:
EqualizeHistLut_Invoker& operator=(const EqualizeHistLut_Invoker&);
cv::Mat& src_;
cv::Mat& dst_;
int* lut_;
};
构造函数保存三个参数。
仿函数是根据灰度变换表lut,把原图变成目标图。
void cv::equalizeHist( InputArray _src, OutputArray _dst )
{
CV_INSTRUMENT_REGION();
CV_Assert( _src.type() == CV_8UC1 );
if (_src.empty())
return;
CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
ocl_equalizeHist(_src, _dst))
Mat src = _src.getMat();
_dst.create( src.size(), src.type() );
Mat dst = _dst.getMat();
CV_OVX_RUN(!ovx::skipSmallImages(src.cols, src.rows),
openvx_equalize_hist(src, dst))
Mutex histogramLockInstance;
const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ;
int hist[hist_sz] = {0,};
int lut[hist_sz];
EqualizeHistCalcHist_Invoker calcBody(src, hist, &histogramLockInstance);
EqualizeHistLut_Invoker lutBody(src, dst, lut);
cv::Range heightRange(0, src.rows);
if(EqualizeHistCalcHist_Invoker::isWorthParallel(src))
parallel_for_(heightRange, calcBody);
else
calcBody(heightRange);
int i = 0;
while (!hist[i]) ++i;
int total = (int)src.total();
if (hist[i] == total)
{
dst.setTo(i);
return;
}
float scale = (hist_sz - 1.f)/(total - hist[i]);
int sum = 0;
for (lut[i++] = 0; i < hist_sz; ++i)
{
sum += hist[i];
lut[i] = saturate_cast(sum * scale);
}
if(EqualizeHistLut_Invoker::isWorthParallel(src))
parallel_for_(heightRange, lutBody);
else
lutBody(heightRange);
}
先是直方图统计,然后是对于纯色图片的特殊处理(直方图均衡结果等于原图),再是计算灰度变换表lut,最后把原图变成目标图。
Ptr createSeparableLinearFilter(
int _srcType, int _dstType,
InputArray __rowKernel, InputArray __columnKernel,
Point _anchor, double _delta,
int _rowBorderType, int _columnBorderType,
const Scalar& _borderValue)
{
Mat _rowKernel = __rowKernel.getMat(), _columnKernel = __columnKernel.getMat();
_srcType = CV_MAT_TYPE(_srcType);
_dstType = CV_MAT_TYPE(_dstType);
int sdepth = CV_MAT_DEPTH(_srcType), ddepth = CV_MAT_DEPTH(_dstType);
int cn = CV_MAT_CN(_srcType);
CV_Assert( cn == CV_MAT_CN(_dstType) );
int rsize = _rowKernel.rows + _rowKernel.cols - 1;
int csize = _columnKernel.rows + _columnKernel.cols - 1;
if( _anchor.x < 0 )
_anchor.x = rsize/2;
if( _anchor.y < 0 )
_anchor.y = csize/2;
int rtype = getKernelType(_rowKernel,
_rowKernel.rows == 1 ? Point(_anchor.x, 0) : Point(0, _anchor.x));
int ctype = getKernelType(_columnKernel,
_columnKernel.rows == 1 ? Point(_anchor.y, 0) : Point(0, _anchor.y));
Mat rowKernel, columnKernel;
bool isBitExactMode = false;
int bdepth = std::max(CV_32F,std::max(sdepth, ddepth));
int bits = 0;
if( sdepth == CV_8U &&
((rtype == KERNEL_SMOOTH+KERNEL_SYMMETRICAL &&
ctype == KERNEL_SMOOTH+KERNEL_SYMMETRICAL &&
ddepth == CV_8U) ||
((rtype & (KERNEL_SYMMETRICAL+KERNEL_ASYMMETRICAL)) &&
(ctype & (KERNEL_SYMMETRICAL+KERNEL_ASYMMETRICAL)) &&
(rtype & ctype & KERNEL_INTEGER) &&
ddepth == CV_16S)) )
{
int bits_ = ddepth == CV_8U ? 8 : 0;
bool isValidBitExactRowKernel = createBitExactKernel_32S(_rowKernel, rowKernel, bits_);
bool isValidBitExactColumnKernel = createBitExactKernel_32S(_columnKernel, columnKernel, bits_);
if (!isValidBitExactRowKernel)
{
CV_LOG_DEBUG(NULL, "createSeparableLinearFilter: bit-exact row-kernel can't be applied: ksize=" << _rowKernel.total());
}
else if (!isValidBitExactColumnKernel)
{
CV_LOG_DEBUG(NULL, "createSeparableLinearFilter: bit-exact column-kernel can't be applied: ksize=" << _columnKernel.total());
}
else
{
bdepth = CV_32S;
bits = bits_;
bits *= 2;
_delta *= (1 << bits);
isBitExactMode = true;
}
}
if (!isBitExactMode)
{
if( _rowKernel.type() != bdepth )
_rowKernel.convertTo( rowKernel, bdepth );
else
rowKernel = _rowKernel;
if( _columnKernel.type() != bdepth )
_columnKernel.convertTo( columnKernel, bdepth );
else
columnKernel = _columnKernel;
}
int _bufType = CV_MAKETYPE(bdepth, cn);
Ptr _rowFilter = getLinearRowFilter(
_srcType, _bufType, rowKernel, _anchor.x, rtype);
Ptr _columnFilter = getLinearColumnFilter(
_bufType, _dstType, columnKernel, _anchor.y, ctype, _delta, bits );
return Ptr( new FilterEngine(Ptr(), _rowFilter, _columnFilter,
_srcType, _dstType, _bufType, _rowBorderType, _columnBorderType, _borderValue ));
}
前2个参数是输入输出图像的格式,接下来2个参数是核分离出来的行向量和列向量。
函数返回一个FilterEngine对象,其中保存了一些需要的信息。
static void ocvSepFilter(int stype, int dtype, int ktype,
uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step,
int width, int height, int full_width, int full_height,
int offset_x, int offset_y,
uchar * kernelx_data, int kernelx_len,
uchar * kernely_data, int kernely_len,
int anchor_x, int anchor_y, double delta, int borderType)
{
Mat kernelX(Size(kernelx_len, 1), ktype, kernelx_data);
Mat kernelY(Size(kernely_len, 1), ktype, kernely_data);
Ptr f = createSeparableLinearFilter(stype, dtype, kernelX, kernelY,
Point(anchor_x, anchor_y),
delta, borderType & ~BORDER_ISOLATED);
Mat src(Size(width, height), stype, src_data, src_step);
Mat dst(Size(width, height), dtype, dst_data, dst_step);
f->apply(src, dst, Size(full_width, full_height), Point(offset_x, offset_y));
};
先创建FilterEngine对象,然后调用它的apply方法进行滤波。
void sepFilter2D(int stype, int dtype, int ktype,
uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step,
int width, int height, int full_width, int full_height,
int offset_x, int offset_y,
uchar * kernelx_data, int kernelx_len,
uchar * kernely_data, int kernely_len,
int anchor_x, int anchor_y, double delta, int borderType)
{
bool res = replacementSepFilter(stype, dtype, ktype,
src_data, src_step, dst_data, dst_step,
width, height, full_width, full_height,
offset_x, offset_y,
kernelx_data, kernelx_len,
kernely_data, kernely_len,
anchor_x, anchor_y, delta, borderType);
if (res)
return;
ocvSepFilter(stype, dtype, ktype,
src_data, src_step, dst_data, dst_step,
width, height, full_width, full_height,
offset_x, offset_y,
kernelx_data, kernelx_len,
kernely_data, kernely_len,
anchor_x, anchor_y, delta, borderType);
}
调用ocvSepFilter
void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy,
int ksize, double scale, double delta, int borderType )
{
CV_INSTRUMENT_REGION();
int stype = _src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
if (ddepth < 0)
ddepth = sdepth;
int dtype = CV_MAKE_TYPE(ddepth, cn);
_dst.create( _src.size(), dtype );
int ktype = std::max(CV_32F, std::max(ddepth, sdepth));
Mat kx, ky;
getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );
if( scale != 1 )
{
// usually the smoothing part is the slowest to compute,
// so try to scale it instead of the faster differentiating part
if( dx == 0 )
kx *= scale;
else
ky *= scale;
}
CV_OCL_RUN(ocl::isOpenCLActivated() && _dst.isUMat() && _src.dims() <= 2 && ksize == 3 &&
(size_t)_src.rows() > ky.total() && (size_t)_src.cols() > kx.total(),
ocl_sepFilter3x3_8UC1(_src, _dst, ddepth, kx, ky, delta, borderType));
CV_OCL_RUN(ocl::isOpenCLActivated() && _dst.isUMat() && _src.dims() <= 2 && (size_t)_src.rows() > kx.total() && (size_t)_src.cols() > kx.total(),
ocl_sepFilter2D(_src, _dst, ddepth, kx, ky, Point(-1, -1), delta, borderType))
Mat src = _src.getMat();
Mat dst = _dst.getMat();
Point ofs;
Size wsz(src.cols, src.rows);
if(!(borderType & BORDER_ISOLATED))
src.locateROI( wsz, ofs );
CALL_HAL(sobel, cv_hal_sobel, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, ddepth, cn,
ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, dx, dy, ksize, scale, delta, borderType&~BORDER_ISOLATED);
CV_OVX_RUN(true,
openvx_sobel(src, dst, dx, dy, ksize, scale, delta, borderType))
//CV_IPP_RUN_FAST(ipp_Deriv(src, dst, dx, dy, ksize, scale, delta, borderType));
sepFilter2D(src, dst, ddepth, kx, ky, Point(-1, -1), delta, borderType );
}
前三个参数是输入图像、输出图像及深度,接下来2个参数是微分的阶。