欢迎光临
我们一直在努力

Opencv源码解析

一,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

1,Mat的数据成员

 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是图像的数据指针。

2,flags

以下宏来自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) &amp; 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的类型

3,UMatData

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是引用计数。

4,step

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];

1,Mat类的create函数

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<int>& _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); }

第一个函数是调用上面的函数。

第二个函数是调用第一个函数。

2,Mat类的copyTo函数

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做深拷贝。

3,Mat类的=运算符

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指针拷贝过来,不拷贝数据。

4,图像截取 Mat(const Mat&, const Rect&)

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; } } 

截取对象的u指针和原对象的u指针是一样的,所以他们是对同一块内存进行引用计数。

5,imwrite

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<Mat>& img_vec, const std::vector<int>& params, bool flipv ) { bool isMultiImg = img_vec.size() > 1; std::vector<Mat> 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<int>& params ) { CV_TRACE_FUNCTION(); CV_Assert(!_img.empty()); std::vector<Mat> 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个元素。

1,图像尺寸上限

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_t>(size.width) <= CV_IO_MAX_IMAGE_WIDTH); CV_Assert(size.height > 0); CV_Assert(static_cast<size_t>(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; }

2,Size

typedef Size_<int> Size2i; typedef Size_<int64> Size2l; typedef Size_<float> Size2f; typedef Size_<double> Size2d; typedef Size2i Size;

Size_是个模板类,只有width和height2个数据成员。

3,***Array

modules\core\include\opencv2\core\mat.hpp

typedef const _InputArray& InputArray; typedef InputArray InputArrayOfArrays;

_InputArray类有3个数据成员:

public: template<typename _Tp> _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函数是利用相位相关法,给两张图片做频域配准。

1,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张图片,第三个是应用窗函数去除图像的边界效应,文档中推荐使用汉宁窗。

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<double> _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<float>(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<double>(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 中的代码:

1,直方图统计

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<uchar>(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函数是判断是否启用并行加速。

2,灰度变换

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<uchar>(rowRange.start); uchar* dptr = dst_.ptr<uchar>(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,把原图变成目标图。

3,直方图均衡

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<VX_KERNEL_EQUALIZE_HISTOGRAM>(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<uchar>(sum * scale); } if(EqualizeHistLut_Invoker::isWorthParallel(src)) parallel_for_(heightRange, lutBody); else lutBody(heightRange); }

先是直方图统计,然后是对于纯色图片的特殊处理(直方图均衡结果等于原图),再是计算灰度变换表lut,最后把原图变成目标图。

1,可分离滤波器的工厂

Ptr<FilterEngine> 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<BaseRowFilter> _rowFilter = getLinearRowFilter( _srcType, _bufType, rowKernel, _anchor.x, rtype); Ptr<BaseColumnFilter> _columnFilter = getLinearColumnFilter( _bufType, _dstType, columnKernel, _anchor.y, ctype, _delta, bits ); return Ptr<FilterEngine>( new FilterEngine(Ptr<BaseFilter>(), _rowFilter, _columnFilter, _srcType, _dstType, _bufType, _rowBorderType, _columnBorderType, _borderValue )); }

前2个参数是输入输出图像的格式,接下来2个参数是核分离出来的行向量和列向量。

函数返回一个FilterEngine对象,其中保存了一些需要的信息。

2,ocvSepFilter、sepFilter2D

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<FilterEngine> 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

3,Sobel

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个参数是微分的阶。

  • 海报
海报图正在生成中...
赞(0) 打赏
声明:
1、本博客不从事任何主机及服务器租赁业务,不参与任何交易,也绝非中介。博客内容仅记录博主个人感兴趣的服务器测评结果及一些服务器相关的优惠活动,信息均摘自网络或来自服务商主动提供;所以对本博客提及的内容不作直接、间接、法定、约定的保证,博客内容也不具备任何参考价值及引导作用,访问者需自行甄别。
2、访问本博客请务必遵守有关互联网的相关法律、规定与规则;不能利用本博客所提及的内容从事任何违法、违规操作;否则造成的一切后果由访问者自行承担。
3、未成年人及不能独立承担法律责任的个人及群体请勿访问本博客。
4、一旦您访问本博客,即表示您已经知晓并接受了以上声明通告。
文章名称:《Opencv源码解析》
文章链接:https://www.456zj.com/20337.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址