通过自定义的结构元素实现结构元素对输入图像对一些对象敏感,对另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出;
常见的结构元素:矩形, 圆,直线,磁盘,钻石;
膨胀操作:
腐蚀操作:
因此,重要的是结构元素的大小,形状,以及膨胀和腐蚀的不同的作用;
如下图所示:
有以下几个目标问题:
其实,上面这张图像还是有一点点难度的,下面处理后的效果还没有很理想;当然这篇文章重要的是为了说明形态学操作的应用,重在原理的理解;后续学到更多的知识,再回头改一改吧;
另外,上面这幅图像是利用代码绘制出来的,代码在这篇文章的最后;当然,如果你的电脑上有绘图软件的画,也可以自己画一些线段和字符;上面这幅图像的特征是:
上面提供的图像是一个彩色图像,为了方便后续处理,执行两步操作:
转换的结果如下图所示:
首先,第一步将彩色图像转化称灰度图像:
// 读取图像 Mat src = imread("/home/chen/dataset/random_line.png"); // 转换称灰度图像 Mat srcGray; cvtColor(src, srcGray, COLOR_BGR2GRAY);
其次,第二步将灰度图像转换称二值图像:
Mat srcBinary; adaptiveThreshold(~srcGray, srcBinary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, -2);
其中,需要注意的有两点:
第一点是关于adaptiveThreshold
这个方法的使用, 使用方法参照:OpenCV-C++ 图像自适应阈值二值化处理adaptiveThreshold
第二点是关于为什么要使用~srcGray
,而不是直接使用srcGray
,这是因为我们想让背景变成黑色,前景变成白色,并配合adativeThreshold
的使用;
如上面二值图像所示,想要提取其中水平线部分,我们需要对图像执行开操作, 即先腐蚀,后膨胀;腐蚀的目的是为了先将除水平线之外的线条过滤掉;
那么,如果过滤呢? 这就需要设计腐蚀操作的结构元素了;
想一下假如,有一个横向的一维结构元素,在腐蚀操作的时候,是不是就将一些纵向的线段腐蚀掉了呢.(背景是黑色,前景是白色)
代码如下:
// 定义水平(横向)结构元素 Mat hline = getStructuringElement(MORPH_RECT, Size(20, 1), Point(-1, -1)); // 水平线 // 开操作: 先腐蚀,后膨胀 Mat dst; erode(srcBinary, dst, hline); // 腐蚀操作, 过滤掉非水平线 dilate(dst, dst, hline); // 膨胀操作
你可能也注意到了,在想要提取水平线的同时,也将字母中的水平线提取出来了.
可以通过增加水平结构元素的长度,即增加Size(20, 1)
, 影响如下:
与提取水平线原理类似:
如上面二值图像所示,想要提取其中垂直线部分,我们需要对图像执行开操作, 即先腐蚀,后膨胀;腐蚀的目的是为了先将除垂直线之外的线条过滤掉;
那么,如果过滤呢? 这就需要设计腐蚀操作的结构元素了;
想一下假如,有一个纵向的一维结构元素,在腐蚀操作的时候,是不是就将一些横向的线段腐蚀掉了呢.(背景是黑色,前景是白色)
代码如下:
// 垂直结构元素 Mat vline = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1)); // 垂直线 // 开操作: 先腐蚀,后膨胀 Mat dst; erode(srcBinary, dst, kernel); dilate(dst, dst, kernel);
同样,也存在相同的问题,就是将字母中的垂直线提取出来,那么相应的解决办法也相同:
尴尬了没有办法完成提取图像中的字符了,构造的图像难度有点高(对于目前初学的我解决不了了);
代码如下:
// 定义结构元素 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1)); // 开操作: 先腐蚀,后膨胀 Mat dst; erode(srcBinary, dst, kernel); dilate(dst, dst, kernel);
其实,原因好像也能理解:主要是因为线段的宽度与字符宽度类似;
或许,再思考一下,直接把水平线和垂直线过滤掉算了,当然重要的是参数的调整;
(尝试着调整了一下,完成不了,shit)
处理过程完整代码:
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(){ // 读取图像 Mat src = imread("/home/chen/dataset/random_line.png"); if (! src.data){ cout << "could not load image." << endl; return -1; } namedWindow("src", WINDOW_AUTOSIZE); imshow("src", src); // 转换成灰度图像 Mat srcGray; cvtColor(src, srcGray, COLOR_BGR2GRAY); namedWindow("srcGray", WINDOW_AUTOSIZE); imshow("srcGray", srcGray); // // 转换为二值图像 Mat srcBinary; adaptiveThreshold(~srcGray, srcBinary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, -2); namedWindow("srcBinary", WINDOW_AUTOSIZE); imshow("srcBinary", srcBinary); // 水平结构元素 Mat hline = getStructuringElement(MORPH_RECT, Size(20, 1), Point(-1, -1)); // 水平线 // 垂直结构元素 Mat vline = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1)); // 垂直线 // 提取字母 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1)); // 开操作: 先腐蚀,后膨胀 Mat dst; erode(srcBinary, dst, kernel); dilate(dst, dst, kernel); // 水平结构元素 Mat hline2 = getStructuringElement(MORPH_RECT, Size(40, 1), Point(-1, -1)); // 水平线 // 垂直结构元素 Mat vline2 = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1)); // 垂直线 Mat temp1, temp2, dst_temp; erode(dst, temp1, hline2); dst_temp = dst - temp1; erode(dst, temp2, vline2); dst = dst_temp - temp2; imshow("temp1", temp1); imshow("temp2", temp2); namedWindow("dst", WINDOW_AUTOSIZE); imshow("dst", dst); // 显示图像 waitKey(0); return 0; }
绘制上面的目标问题图像:
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(){ // 创建一张空白图像 Mat src = Mat(Size(512, 512), CV_8UC3, Scalar(255, 255, 255)); RNG rng(12345); // 随机生成一些水平线 Point p1, p2; Scalar color; const int MAX_THICKNESS = 5; int thickness; const int NUM_HORIZONTAL_LINE = 3; for (int i = 0; i <= NUM_HORIZONTAL_LINE; i++){ p1.x = rng.uniform(0, src.cols); p1.y = rng.uniform(MAX_THICKNESS, src.rows - MAX_THICKNESS); p2.x = rng.uniform(0, src.cols); p2.y = p1.y; color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); thickness = rng.uniform(1, MAX_THICKNESS); line(src, p1, p2, color, thickness); } // 随机生成一些垂直线 const int NUM_VERTICAL_LINE = 3; for (int i = 0; i <= NUM_VERTICAL_LINE; i++){ p1.x = rng.uniform(MAX_THICKNESS, src.cols- MAX_THICKNESS); p1.y = rng.uniform(0, src.rows); p2.x = p1.x; p2.y = rng.uniform(0, src.rows); color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); thickness = rng.uniform(1, MAX_THICKNESS); line(src, p1, p2, color, thickness); } // 在随机位置生成一些数字 putText(src, "A", Point(90, 128), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 255), 3); putText(src, "B", Point(200, 128), FONT_HERSHEY_COMPLEX, 2.5, Scalar(0, 0, 255), 3); putText(src, "E", Point(320, 128), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 3); // 显示图像 namedWindow("src", WINDOW_AUTOSIZE); imshow("src", src); waitKey(0); // 保存图像 imwrite("/home/chen/dataset/random_line.png", src); return 0; }