内容来源于《opencv4应用开发入门、进阶与工程化实践》
膨胀操作与腐蚀操作
膨胀操作一定程度上会把相邻的对象连接起来成为一个对象;
腐蚀操作会让对象面积变小或者擦除小的对象。
//膨胀操作
void cv::dilate(
InputArray src
OutputArray dst
InputArray kernel
Point anchor=Point(-1,-1)
int iterations = 1
int borderType = BORDER_CONSTANT //边缘处理方式
const Scalar & borderValue=morphologyDefaultBorderValue()
)
//腐蚀操作
void cv::erode(
InputArray src
OutputArray dst
InputArray kernel
Point anchor=Point(-1,-1)
int iterations=1
int borderType=BORDER_CONSTANT
const Scalar & borderValue=morphoolgyDefaultBorderValue()
)
//示例代码
void MorphologyOpDemo::dilate_erode_demo(Mat &image) {
Mat dst1, dst2;
Mat se = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
dilate(image, dst1, se);
erode(image, dst2, se);
imshow("膨胀", dst1);
imshow("腐蚀", dst2);
}
开闭操作
开操作:先腐蚀后膨胀,可以删除二值图像中较小的干扰块,解决图像二值化后噪点过多的问题。
闭操作:先膨胀后腐蚀,可以填充二值图像对象内部的孔洞区域,以获得更完整的前景图像。
void cv::morphologyEx(
InputArray src
OutputArray dst
int op //形态学操作
InputArray kernel
Point anchor=Point(-1,-1)
int iterations=1
int borderType=BORDER_CONSTANT
const Scalar & borderValue=morphologyDefaultBorderValue)
//开操作设置op=MORPH_OPEN
//闭操作设置op=MORPH_CLOSE
//示例代码
void MorphologyOpDemo::open_close_demo(Mat &image) {
Mat se = getStructuringElement(MORPH_RECT, Size(9, 9), Point(-1, -1));
// open
Mat result1;
erode(image, result1, se);
dilate(result1, result1, se);
imshow("开操作", result1);
// close
Mat result2;
dilate(image, result2, se);
erode(result2, result2, se);
imshow("闭操作", result2);
Mat result;
morphologyEx(image, result, MORPH_OPEN, se);
imshow("MORPH_OPEN", result);
morphologyEx(image, result, MORPH_CLOSE, se);
imshow("MORPH_CLOSE", result);
}
形态学梯度
不但可以快速得到二值图像中各个对象的轮廓、提取对象的边缘,还能在对灰度实现形态学梯度操作后再进行二值化,甚至有时能得到比Canny边缘检测效果更好的图像轮廓边缘。
- 基本梯度=膨胀-腐蚀 op=MORPH_GRADIENT
- 内梯度=原图-腐蚀
- 外梯度=膨胀-原图
//示例代码
void MorphologyOpDemo::gradient_demo(Mat &image) {
Mat se = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
Mat basic_grad, ex_grad, in_grad;
Mat di, er;
dilate(image, di, se);
erode(image, er, se);
// 基本梯度
morphologyEx(image, basic_grad, MORPH_GRADIENT, se);
// 外梯度
subtract(di, image, ex_grad);
// 内梯度
subtract(image, er, in_grad);
// display
imshow("基本梯度", basic_grad);
imshow("外梯度", ex_grad);
imshow("内梯度", in_grad);
}
形态学梯度提取边缘,全程不用手动输入阈值即可实现图像的边缘检测。
void MorphologyOpDemo::gradient_edges(Mat &image) {
Mat se = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
Mat gray, edges;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat basic_grad;
morphologyEx(gray, basic_grad, MORPH_GRADIENT, se);
threshold(basic_grad, edges, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("边缘检测", edges);
}
顶帽与黑帽
顶帽与黑帽可以很好的实现从二值图像中提取小斑点对象的功能。
顶帽=原图-开操作 op=MORPH_TOPHAT
黑帽=闭操作-原图 op=MORPH_BLACKHAT
//多次输入图像的顶帽和黑帽操作可用于提取图像中不同颗粒度的对象
void MorphologyOpDemo::hats_demo(Mat &image) {
Mat se = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
Mat binary;
threshold(image, binary, 127, 255, THRESH_BINARY);
Mat tophat, blackhat;
morphologyEx(binary, tophat, MORPH_TOPHAT, se);
morphologyEx(binary, blackhat, MORPH_BLACKHAT, se);
imshow("顶帽", tophat);
imshow("黑帽", blackhat);
}
击中不击中
可以实现对象的细化和剪枝操作。击中不击中操作对二值图像的模式匹配和轮廓发现都是非常有作用的。
示例代码:实现对二值图像的边缘轮廓提取,两个结构元素分别提取左上和右下的边的形状,然后相加得到图像轮廓。
void MorphologyOpDemo::hitandmiss_demo(Mat &image) {
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat se1 = (Mat_<int>(3, 3) << 1, 0, 0, 0, -1, 0, 0, 0, 0);
Mat se2 = (Mat_<int>(3, 3) << 0, 0, 0, 0, -1, 0, 0, 0, 1);
Mat h1, h2, result;
morphologyEx(binary, h1, MORPH_HITMISS, se1);
morphologyEx(binary, h2, MORPH_HITMISS, se2);
add(h1, h2, result);
imshow("击中击不中", result);
}
结构元素
opencv在结构元素定义中支持矩形、椭圆形、和十字交叉。
//检测水平线与垂直线
void MorphologyOpDemo::hvlines_demo(Mat &image) {
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
Mat h_line = getStructuringElement(MORPH_RECT, Size(25, 1), Point(-1, -1));
Mat v_line = getStructuringElement(MORPH_RECT, Size(1, 25), Point(-1, -1));
Mat hResult, vResult;
morphologyEx(binary, hResult, MORPH_OPEN, h_line);
morphologyEx(binary, vResult, MORPH_OPEN, v_line);
imshow("水平线", hResult);
imshow("垂直线", vResult);
}
//检测十字交叉点
void MorphologyOpDemo::cross_demo(Mat &image) {
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
Mat cross_se = getStructuringElement(MORPH_CROSS, Size(11, 11), Point(-1, -1));
Mat result;
morphologyEx(binary, result, MORPH_OPEN, cross_se);
imshow("十字交叉", result);
}
距离变换
距离变换的结果是得到一张与输入图像类似的灰度图,但是灰度图只出现在前景区域,并且越是远离背景边缘的像素点,其灰度值越大。
一个最常见的距离变换算法就是通过连续的腐蚀操作来实现的。腐蚀操作的停止条件是所有前景像素完全被腐蚀,这样根据腐蚀操作的先后顺序,就可以得到各个前景像素点到前景中心骨架像素点的距离。然后根据各个像素点的距离值,设置不同的灰度值。
void cv::distanceTransform(
InputArray src
OutputArray dst
OutputArray labels
int distanceType
int maskSize
int labelType=DIST_LABEL_CCOMP
)
void MorphologyOpDemo::distance_demo(Mat &image) {
Mat hsv, mask;
cvtColor(image, hsv, COLOR_BGR2HSV);
inRange(hsv, Scalar(150, 200, 200), Scalar(180, 255, 255), mask);
Mat dst;
distanceTransform(mask, dst, DIST_L2, 3, CV_32F);
normalize(dst, dst, 0, 1, NORM_MINMAX);
imshow("距离变换", dst);
}
分水岭分割
不明白的可以看这个文章,写的很好:https://zhuanlan.zhihu.com/p/67741538
void MorphologyOpDemo::wateshed_demo(Mat &image) {
// 二值化
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
// 开操作
Mat opening, dist;
Mat se = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
morphologyEx(binary, opening, MORPH_OPEN, se); //开操作可以去除细小的白色噪点,更好的提取前景
// 背景
Mat bg;
dilate(binary, bg, se); //膨胀运算使得一部分背景成了物体的边界,所以得到的图像中黑色区域就肯定是背景
// 距离变换
distanceTransform(opening, dist, DIST_L2, 3, CV_32F); //灰度值只出现在前景的前景图
normalize(dist, dist, 0, 1, NORM_MINMAX);
// 生成markers
Mat objects, markers;
threshold(dist, objects, 0.7, 1.0, THRESH_BINARY); //二值化,得到更好的前景图
objects = objects*255.0;
objects.convertTo(objects, CV_8U);
int num = connectedComponents(objects, markers, 8, 4);
// 背景为1, unknown = 0, 其它label大于1
markers = markers + 1;
Mat unknown;
subtract(bg, objects, unknown); //膨胀图减前景图剩下的是unknown
for (int row = 0; row < unknown.rows; row++) {
for (int col = 0; col < unknown.cols; col++) {
int b = unknown.at<uchar>(row, col);
if (b > 0) {
markers.at<int>(row, col) = 0;
}
}
}
imshow("距离变换", objects);
// 分水岭变换
watershed(image, markers);
// 构建颜色查找表
RNG rng(12345);
Vec3b background_color(255, 255, 255);
std::vector<Vec3b> colors_table;
for (int i = 1; i < num; i++) {
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
colors_table.push_back(Vec3b(b, g, r));
}
// 绘制
for (int row = 0; row < image.rows; row++) {
for (int col = 0; col < image.cols; col++) {
int b = bg.at<uchar>(row, col);
int c = markers.at<int>(row, col);
if (b > 0) {
image.at<Vec3b>(row, col) = colors_table[c+1];
}
else {
image.at<Vec3b>(row, col) = background_color;
}
}
}
imshow("分水岭变换", image);
}