Morphological Operations: A set of operations that process images based on shapes.Morphological operations apply a structuring element to an input image and generate an output image.
The most basic morphological operations are two: Erosion and Dilation. They have a wide array of uses, i.e.:
(1)、Removing noise.
(2)、Isolation of individual elements and joining disparate elements in an image.
(3)、Finding of intensity bumps or holes in an image.
数学形态学可以理解为一种滤波行为,因此也称为形态学滤波。滤波中用到的滤波器(kernal),在形态学中称为结构元素。结构元素往往是由一个特殊的形状构成,如线条、矩形、圆等。
OpenCV中的erode函数支持多通道,各个通道腐蚀处理过程独立。腐蚀针对白色部分(高亮部分)。腐蚀即求局部最小值的操作,图像A与核B作卷积运算,计算核B覆盖区域的像素点的最小值,并把这个值赋值给锚点(anchor point)指定的像素。
Erosion:
(1)、This operation is the sister of dilation. What this does is to compute a local minimum over the area of the kernel.
(2)、As the kernel B is scanned over the image, we compute the minimal pixel value overlapped by B and replace the image pixel under the anchor point with that minimal value.
目前fbc_cv库中支持uchar和float两种数据类型,经测试,与OpenCV3.1结果完全一致。
实现代码erode.hpp:
// fbc_cv is free software and uses the same licence as OpenCV
// Email: fengbingchun@163.com
#ifndef FBC_CV_ERODE_HPP_
#define FBC_CV_ERODE_HPP_
/* reference: include/opencv2/imgproc.hpp
modules/imgproc/src/morph.cpp
*/
#include <typeinfo>
#include "core/mat.hpp"
#include "imgproc.hpp"
#include "filterengine.hpp"
#include "core/core.hpp"
#include "morph.hpp"
namespace fbc {
// Erodes an image by using a specific structuring element
// \f[\texttt{ dst } (x, y) = \min _{ (x',y') : \, \texttt{ element } (x',y') \ne0 } \texttt{ src } (x + x',y+y')\f]
// In case of multi - channel images, each channel is processed independently.
// Erosion can be applied several ( iterations ) times.
// support type: uchar/float, multi-channels
template<typename _Tp, int chs>
int erode(const Mat_<_Tp, chs>& src, Mat_<_Tp, chs>& dst, Mat_<uchar, 1>& kernel,
Point anchor = Point(-1, -1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = Scalar::all(DBL_MAX))
{
FBC_Assert(typeid(uchar).name() == typeid(_Tp).name() || typeid(float).name() == typeid(_Tp).name()); // uchar || float
if (dst.empty()) {
dst = Mat_<_Tp, chs>(src.rows, src.cols);
} else {
FBC_Assert(src.rows == dst.rows && src.cols == dst.cols);
}
Size ksize = !kernel.empty() ? kernel.size() : Size(3, 3);
anchor = normalizeAnchor(anchor, ksize);
if (iterations == 0 || kernel.rows * kernel.cols == 1) {
src.copyTo(dst);
return 0;
}
if (kernel.empty()) {
kernel = Mat_<uchar, 1>(1 + iterations * 2, 1 + iterations * 2);
getStructuringElement(kernel, MORPH_RECT, Size(1 + iterations * 2, 1 + iterations * 2));
anchor = Point(iterations, iterations);
iterations = 1;
} else if (iterations > 1 && countNonZero(kernel) == kernel.rows * kernel.cols) {
anchor = Point(anchor.x*iterations, anchor.y*iterations);
kernel = Mat_<uchar, 1>(ksize.height + (iterations - 1)*(ksize.height - 1), ksize.width + (iterations - 1)*(ksize.width - 1));
getStructuringElement(kernel, MORPH_RECT,
Size(ksize.width + (iterations - 1)*(ksize.width - 1), ksize.height + (iterations - 1)*(ksize.height - 1)), anchor);
iterations = 1;
}
anchor = normalizeAnchor(anchor, kernel.size());
Ptr<BaseRowFilter> rowFilter;
Ptr<BaseColumnFilter> columnFilter;
Ptr<BaseFilter> filter2D;
if (countNonZero(kernel) == kernel.rows*kernel.cols) {
// rectangular structuring element
rowFilter = getMorphologyRowFilter<_Tp, chs>(0, kernel.cols, anchor.x);
columnFilter = getMorphologyColumnFilter<_Tp, chs>(0, kernel.rows, anchor.y);
} else {
filter2D = getMorphologyFilter<_Tp, chs>(0, kernel, anchor);
}
Scalar borderValue_ = borderValue;
if (borderType == BORDER_CONSTANT && borderValue_ == Scalar::all(DBL_MAX)) {
if (sizeof(_Tp) == 1) // CV_8U
borderValue_ = Scalar::all((double)UCHAR_MAX);
else // CV_32F
borderValue_ = Scalar::all((double)FLT_MAX);
}
Ptr<FilterEngine<_Tp, _Tp, _Tp, chs, chs, chs>> f = makePtr<FilterEngine<_Tp, _Tp, _Tp, chs, chs, chs>>(filter2D, rowFilter, columnFilter, borderType, borderType, borderValue_);
f->apply(src, dst);
for (int i = 1; i < iterations; i++)
f->apply(dst, dst);
return 0;
}
} // namespace fbc
#endif // FBC_CV_ERODE_HPP_
测试代码test_erode.cpp:
#include "test_erode.hpp"
#include <assert.h>
#include <erode.hpp>
#include <opencv2/opencv.hpp>
int test_erode_uchar()
{
cv::Mat matSrc = cv::imread("E:/GitCode/OpenCV_Test/test_images/lena.png", 1);
if (!matSrc.data) {
std::cout << "read image fail" << std::endl;
return -1;
}
int width = matSrc.cols;
int height = matSrc.rows;
for (int elem = 0; elem < 3; elem++) {
for (int size = 0; size < 10; size++) {
for (int iterations = 1; iterations < 5; iterations++) {
int type;
if (elem == 0){ type = fbc::MORPH_RECT; }
else if (elem == 1){ type = fbc::MORPH_CROSS; }
else if (elem == 2) { type = fbc::MORPH_ELLIPSE; }
fbc::Mat_<uchar, 1> element(2 * size + 1, 2 * size + 1);
fbc::getStructuringElement(element, type, fbc::Size(2 * size + 1, 2 * size + 1), fbc::Point(size, size));
int type_;
if (elem == 0){ type_ = cv::MORPH_RECT; }
else if (elem == 1){ type_ = cv::MORPH_CROSS; }
else if (elem == 2) { type_ = cv::MORPH_ELLIPSE; }
cv::Mat element_ = cv::getStructuringElement(type_, cv::Size(2 * size + 1, 2 * size + 1), cv::Point(size, size));
assert(element.rows == element_.rows && element.cols == element.cols && element.step == element_.step);
for (int y = 0; y < element.rows; y++) {
const fbc::uchar* p1 = element.ptr(y);
const uchar* p2 = element_.ptr(y);
for (int x = 0; x < element.step; x++) {
assert(p1[x] == p2[x]);
}
}
fbc::Mat3BGR mat1(height, width, matSrc.data);
fbc::Mat3BGR mat2(height, width);
fbc::erode(mat1, mat2, element, fbc::Point(-1, -1), iterations, 0, fbc::Scalar::all(128));
cv::Mat mat1_(height, width, CV_8UC3, matSrc.data);
cv::Mat mat2_;
cv::erode(mat1_, mat2_, element_, cv::Point(-1, -1), iterations, 0, cv::Scalar::all(128));
assert(mat2.rows == mat2_.rows && mat2.cols == mat2_.cols && mat2.step == mat2_.step);
for (int y = 0; y < mat2.rows; y++) {
const fbc::uchar* p1 = mat2.ptr(y);
const uchar* p2 = mat2_.ptr(y);
for (int x = 0; x < mat2.step; x++) {
assert(p1[x] == p2[x]);
}
}
}
}
}
return 0;
}
int test_erode_float()
{
cv::Mat matSrc = cv::imread("E:/GitCode/OpenCV_Test/test_images/lena.png", 1);
if (!matSrc.data) {
std::cout << "read image fail" << std::endl;
return -1;
}
cv::cvtColor(matSrc, matSrc, CV_BGR2GRAY);
matSrc.convertTo(matSrc, CV_32FC1);
int width = matSrc.cols;
int height = matSrc.rows;
for (int elem = 0; elem < 3; elem++) {
for (int size = 0; size < 10; size++) {
for (int iterations = 1; iterations < 5; iterations++) {
int type;
if (elem == 0){ type = fbc::MORPH_RECT; }
else if (elem == 1){ type = fbc::MORPH_CROSS; }
else if (elem == 2) { type = fbc::MORPH_ELLIPSE; }
fbc::Mat_<uchar, 1> element(2 * size + 1, 2 * size + 1);
fbc::getStructuringElement(element, type, fbc::Size(2 * size + 1, 2 * size + 1), fbc::Point(size, size));
int type_;
if (elem == 0){ type_ = cv::MORPH_RECT; }
else if (elem == 1){ type_ = cv::MORPH_CROSS; }
else if (elem == 2) { type_ = cv::MORPH_ELLIPSE; }
cv::Mat element_ = cv::getStructuringElement(type_, cv::Size(2 * size + 1, 2 * size + 1), cv::Point(size, size));
assert(element.rows == element_.rows && element.cols == element.cols && element.step == element_.step);
for (int y = 0; y < element.rows; y++) {
const fbc::uchar* p1 = element.ptr(y);
const uchar* p2 = element_.ptr(y);
for (int x = 0; x < element.step; x++) {
assert(p1[x] == p2[x]);
}
}
fbc::Mat_<float, 1> mat1(height, width, matSrc.data);
fbc::Mat_<float, 1> mat2(height, width);
fbc::erode(mat1, mat2, element, fbc::Point(-1, -1), iterations, 0, fbc::Scalar::all(128));
cv::Mat mat1_(height, width, CV_32FC1, matSrc.data);
cv::Mat mat2_;
cv::erode(mat1_, mat2_, element_, cv::Point(-1, -1), iterations, 0, cv::Scalar::all(128));
assert(mat2.rows == mat2_.rows && mat2.cols == mat2_.cols && mat2.step == mat2_.step);
for (int y = 0; y < mat2.rows; y++) {
const fbc::uchar* p1 = mat2.ptr(y);
const uchar* p2 = mat2_.ptr(y);
for (int x = 0; x < mat2.step; x++) {
assert(p1[x] == p2[x]);
}
}
}
}
}
return 0;
}