OPENCV第七章-图像形态学操作
图像连通域分析
连通域分割原理
一般对图像进行连通域分析时候,一般会先对图像进行二值化处理
两遍法:遍历第一行,遇到黄色框设置为1,遍历第2行时,如果上方与左方有标记数值,则把该数值设置为二者较小的数值,所以(2,1)和(2,2)先被标记为2,而(2,3)被标记为1,遍历第四行时候把(4,2)遍历标记为3
可以得到
接下来进行第二次遍历,将两个临近的像素相互同一成一个值
得到

连通域分割函数
还有最后一个参数用于选择算法
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
//对图像进行距离变换
Mat img = imread("rice.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat rice, riceBW;
//将图像转成二值图像,用于统计连通域
cvtColor(img, rice, COLOR_BGR2GRAY);
threshold(rice, riceBW, 50, 255, THRESH_BINARY);
//生成随机颜色,用于区分不同连通域
RNG rng(10086);
Mat out;
int number = connectedComponents(riceBW, out, 8, CV_16U); //统计图像中连通域的个数
vector<Vec3b> colors;
for (int i = 0; i < number; i++)
{
//使用均匀分布的随机数确定颜色
Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
colors.push_back(vec3);
}
//以不同颜色标记出不同的连通域
Mat result = Mat::zeros(rice.size(), img.type());
int w = result.cols;
int h = result.rows;
for (int row = 0; row < h; row++)
{
for (int col = 0; col < w; col++)
{
int label = out.at<uint16_t>(row, col);
if (label == 0) //背景的黑色不改变,如果像素为0说明为黑色背景
{
continue;
}
result.at<Vec3b>(row, col) = colors[label];
}
}
//显示结果
imshow("原图", img);
imshow("标记后的图像", result);
waitKey(0);
return 0;
}
统计连通域并存放统计信息
连通域存放的信息

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
//对图像进行距离变换
Mat img = imread("rice.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
imshow("原图", img);
Mat rice, riceBW;
//将图像转成二值图像,用于统计连通域
cvtColor(img, rice, COLOR_BGR2GRAY);
threshold(rice, riceBW, 50, 255, THRESH_BINARY);
//生成随机颜色,用于区分不同连通域
RNG rng(10086);
Mat out, stats, centroids;
//统计图像中连通域的个数
int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
vector<Vec3b> colors;
for (int i = 0; i < number; i++)
{
//使用均匀分布的随机数确定颜色
Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
colors.push_back(vec3);
}
//以不同颜色标记出不同的连通域
Mat result = Mat::zeros(rice.size(), img.type());
int w = result.cols;
int h = result.rows;
for (int i = 1; i < number; i++)
{
// 中心位置
int center_x = centroids.at<double>(i, 0);
int center_y = centroids.at<double>(i, 1);
//矩形边框
int x = stats.at<int>(i, CC_STAT_LEFT);
int y = stats.at<int>(i, CC_STAT_TOP);
int w = stats.at<int>(i, CC_STAT_WIDTH);
int h = stats.at<int>(i, CC_STAT_HEIGHT);
int area = stats.at<int>(i, CC_STAT_AREA);
// 中心位置绘制
circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
// 外接矩形
Rect rect(x, y, w, h);
rectangle(img, rect, colors[i], 1, 8, 0);
putText(img, format("%d", i), Point(center_x, center_y),
FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
cout << "number: " << i << ",area: " << area << endl;
}
//显示结果
imshow("标记后的图像", img);
waitKey(0);
return 0;
}
距离变换
像素间距离
距离变换函数
计算的是所有非零像素到零像素的距离,模板尺寸建议为5

图像腐蚀
目的:去除图像中微笑物体,分离较近的两个物体
图像腐蚀原理

结构元素遍历整个图像,当遇到结构元素的四个像素不能覆盖则删除该像素
图像腐蚀函数
获得结构元素
腐蚀函数
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
//绘制包含区域函数
void drawState(Mat& img, int number, Mat centroids, Mat stats, String str) {
RNG rng(10086);
vector<Vec3b> colors;
for (int i = 0; i < number; i++)
{
//使用均匀分布的随机数确定颜色
Vec3b vec3 = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
colors.push_back(vec3);
}
for (int i = 1; i < number; i++)
{
// 中心位置
int center_x = centroids.at<double>(i, 0);
int center_y = centroids.at<double>(i, 1);
//矩形边框
int x = stats.at<int>(i, CC_STAT_LEFT);
int y = stats.at<int>(i, CC_STAT_TOP);
int w = stats.at<int>(i, CC_STAT_WIDTH);
int h = stats.at<int>(i, CC_STAT_HEIGHT);
// 中心位置绘制
circle(img, Point(center_x, center_y), 2, Scalar(0, 255, 0), 2, 8, 0);
// 外接矩形
Rect rect(x, y, w, h);
rectangle(img, rect, colors[i], 1, 8, 0);
putText(img, format("%d", i), Point(center_x, center_y),
FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1);
}
imshow(str, img);
}
int main()
{
//生成用于腐蚀的原图像
Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0);
Mat struct1, struct2;
struct1 = getStructuringElement(0, Size(3, 3)); //矩形结构元素
struct2 = getStructuringElement(1, Size(3, 3)); //十字结构元素
Mat erodeSrc; //存放腐蚀后的图像
erode(src, erodeSrc, struct2);
namedWindow("src", WINDOW_GUI_NORMAL);
namedWindow("erodeSrc", WINDOW_GUI_NORMAL);
imshow("src", src);
imshow("erodeSrc", erodeSrc);
Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
Mat LearnCV_write = imread("LearnCV_white.png", IMREAD_ANYCOLOR);
if (LearnCV_black.empty()|| LearnCV_write.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat erode_black1, erode_black2, erode_write1, erode_write2;
//黑背景图像腐蚀
erode(LearnCV_black, erode_black1, struct1);
erode(LearnCV_black, erode_black2, struct2);
imshow("LearnCV_black", LearnCV_black);
imshow("erode_black1", erode_black1);
imshow("erode_black2", erode_black2);
//白背景腐蚀
erode(LearnCV_write, erode_write1, struct1);
erode(LearnCV_write, erode_write2, struct2);
imshow("LearnCV_write", LearnCV_write);
imshow("erode_write1", erode_write1);
imshow("erode_write2", erode_write2);
//验证腐蚀对小连通域的去除
Mat img = imread("rice.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat img2;
copyTo(img, img2, img); //克隆一个单独的图像,用于后期图像绘制
Mat rice, riceBW;
//将图像转成二值图像,用于统计连通域
cvtColor(img, rice, COLOR_BGR2GRAY);
threshold(rice, riceBW, 50, 255, THRESH_BINARY);
Mat out, stats, centroids;
//统计图像中连通域的个数
int number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
drawState(img, number, centroids, stats, "未腐蚀时统计连通域"); //绘制图像
erode(riceBW, riceBW, struct1); //对图像进行腐蚀
number = connectedComponentsWithStats(riceBW, out, stats, centroids, 8, CV_16U);
drawState(img2, number, centroids, stats, "腐蚀后统计连通域"); //绘制图像
waitKey(0);
return 0;
}
图像膨胀
结构中心元素和原图像非零元素覆盖,然后把结构元素的其他位置覆盖为1,知道覆盖完原图的所有非零元素
结构元素和上一个函数相同

图像膨胀操作函数
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
//生成用于腐蚀的原图像
Mat src = (Mat_<uchar>(6, 6) << 0, 0, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0);
Mat struct1, struct2;
struct1 = getStructuringElement(0, Size(3, 3)); //矩形结构元素
struct2 = getStructuringElement(1, Size(3, 3)); //十字结构元素
Mat erodeSrc; //存放膨胀后的图像
dilate(src, erodeSrc, struct2);
namedWindow("src", WINDOW_GUI_NORMAL);
namedWindow("dilateSrc", WINDOW_GUI_NORMAL);
imshow("src", src);
imshow("dilateSrc", erodeSrc);
Mat LearnCV_black = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
Mat LearnCV_write = imread("LearnCV_white.png", IMREAD_ANYCOLOR);
if (LearnCV_black.empty() || LearnCV_write.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat dilate_black1, dilate_black2, dilate_write1, dilate_write2;
//黑背景图像膨胀
dilate(LearnCV_black, dilate_black1, struct1);
dilate(LearnCV_black, dilate_black2, struct2);
imshow("LearnCV_black", LearnCV_black);
imshow("dilate_black1", dilate_black1);
imshow("dilate_black2", dilate_black2);
//白背景图像膨胀
dilate(LearnCV_write, dilate_write1, struct1);
dilate(LearnCV_write, dilate_write2, struct2);
imshow("LearnCV_write", LearnCV_write);
imshow("dilate_write1", dilate_write1);
imshow("dilate_write2", dilate_write2);
//比较膨胀和腐蚀的结果
Mat erode_black1, resultXor, resultAnd;
erode(LearnCV_black, erode_black1, struct1);
bitwise_xor(erode_black1, dilate_write1, resultXor);
bitwise_and(erode_black1, dilate_write1, resultAnd);
imshow("resultXor", resultXor);
imshow("resultAnd", resultAnd);
waitKey(0);
return 0;
}
形态学应用
开运算

闭运算

形态学梯度

顶帽运算

黑帽运算

击中击不中变换
原图像中必须和结构元素必须有相同布局才行

相关函数
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
//用于验证形态学应用的二值化矩阵
Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
namedWindow("src", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("src", src);
//3×3矩形结构元素
Mat kernel = getStructuringElement(0, Size(3, 3));
//对二值化矩阵进行形态学操作
Mat open, close, gradient, tophat, blackhat, hitmiss;
//对二值化矩阵进行开运算
morphologyEx(src, open, MORPH_OPEN, kernel);
namedWindow("open", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("open", open);
//对二值化矩阵进行闭运算
morphologyEx(src, close, MORPH_CLOSE, kernel);
namedWindow("close", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("close", close);
//对二值化矩阵进行梯度运算
morphologyEx(src, gradient, MORPH_GRADIENT, kernel);
namedWindow("gradient", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("gradient", gradient);
//对二值化矩阵进行顶帽运算
morphologyEx(src, tophat, MORPH_TOPHAT, kernel);
namedWindow("tophat", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("tophat", tophat);
//对二值化矩阵进行黑帽运算
morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);
namedWindow("blackhat", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("blackhat", blackhat);
//对二值化矩阵进行击中击不中变换
morphologyEx(src, hitmiss, MORPH_HITMISS, kernel);
namedWindow("hitmiss", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
imshow("hitmiss", hitmiss);
//用图像验证形态学操作效果
Mat keys = imread("keys.jpg", IMREAD_GRAYSCALE);
imshow("原图像", keys);
threshold(keys, keys, 80, 255, THRESH_BINARY);
imshow("二值化后的keys", keys);
//5×5矩形结构元素
Mat kernel_keys = getStructuringElement(0, Size(5, 5));
Mat open_keys, close_keys, gradient_keys, tophat_keys, blackhat_keys, hitmiss_keys;
//对图像进行开运算
morphologyEx(keys, open_keys, MORPH_OPEN, kernel_keys);
imshow("open_keys", open_keys);
//对图像进行闭运算
morphologyEx(keys, close_keys, MORPH_CLOSE, kernel_keys);
imshow("close_keys", close_keys);
//对图像进行梯度运算
morphologyEx(keys, gradient_keys, MORPH_GRADIENT, kernel_keys);
imshow("gradient_keys", gradient_keys);
//对图像进行顶帽运算
morphologyEx(keys, tophat_keys, MORPH_TOPHAT, kernel_keys);
imshow("tophat_keys", tophat_keys);
//对图像进行黑帽运算
morphologyEx(keys, blackhat_keys, MORPH_BLACKHAT, kernel_keys);
imshow("blackhat_keys", blackhat_keys);
//对图像进行击中击不中变换
morphologyEx(keys, hitmiss_keys, MORPH_HITMISS, kernel_keys);
imshow("hitmiss_keys", hitmiss_keys);
waitKey(0);
return 0;
}
图像细化(骨架化)
删除图像较宽的部分
遍历两次代码,将满足左侧和满足右侧条件的像素指定为待删除点
然后把待删除点全部删除
相关函数
thinning函数在扩展模块里面,需要额外安装
我没装凑活看把
#include <opencv2\opencv.hpp>
#include <opencv2\ximgproc.hpp> //细化函数thining所在的头文件
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//中文字进行细化
Mat img = imread("LearnCV_black.png", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
//英文字+实心圆和圆环细化
Mat words = Mat::zeros(100, 200, CV_8UC1); //创建一个黑色的背景图片
putText(words, "Learn", Point(30, 30), 2, 1, Scalar(255), 2); //添加英文
putText(words, "OpenCV 4", Point(30, 60), 2, 1, Scalar(255), 2);
circle(words, Point(80, 75), 10, Scalar(255), -1); //添加实心圆
circle(words, Point(130, 75), 10, Scalar(255), 3); //添加圆环
//进行细化
Mat thin1, thin2;
ximgproc::thinning(img, thin1, 0); //注意类名
ximgproc::thinning(words, thin2, 0);
//显示处理结果
imshow("thin1", thin1);
imshow("img", img);
namedWindow("thin2", WINDOW_NORMAL);
imshow("thin2", thin2);
namedWindow("words", WINDOW_NORMAL);
imshow("words", words);
waitKey(0);
return 0;
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 name1110
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果