图像连通区域
图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域,连通域分析是指在图像中寻找出彼此互相独立的连通域并将其标记出来。提取图像中不同的连通域是图像处理中较为常用的方法,例如在车牌识别、文字识别、目标检测等领域对感兴趣区域分割与识别。一般情况下,一个连通域内只包含一个像素值,因此为了防止像素值波动对提取不同连通域的影响,连通域分析常处理的是二值化后的图像。
邻域
邻域即相邻得区域,opencv中有以下两种形式得领域
- 4-邻域:必须在水平和垂直方向上相邻,相邻的两个像素坐标必须只有一位不同而且只能相差1个像素
- 8-邻域: 九宫格形式,相邻的两个像素坐标必须只有一位不同而且只能相差1个像素
图像邻域分析法
两遍扫描法
两遍扫描法会遍历两次图像,第一次遍历图像时会给每一个非0像素赋予一个数字标签,当某个像素的上方和左侧邻域内的像素已经有数字标签时,取两者中的最小值作为当前像素的标签,否则赋予当前像素一个新的数字标签。第一次遍历图像的时候同一个连通域可能会被赋予一个或者多个不同的标签
种子填充法
首先将所有非0像素放到一个集合中,之后在集合中随机选出一个像素作为种子像素,根据邻域关系不断扩充种子像素所在的连通域,并在集合中删除掉扩充出的像素,直到种子像素所在的连通域无法扩充,之后再从集合中随机选取一个像素作为新的种子像素,重复上述过程直到集合中没有像素(类似DFS)
连通区域操作
不带统计信息的API
int connectedComponents(InputArray image, OutputArray labels,int connectivity = 8, int ltype = CV_32S);
/*******************************************************************
* image: 输入二值图像
* labels: 输出图像
* connectivity: 邻域
* ltype: 输出图深度
*********************************************************************/
带有统计信息的API
int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids,int connectivity, int ltype, int ccltype);
/*******************************************************************
* image: 输入二值图像
* labels: 输出图像
* stats: 统计信息,包括每个组件的位置、宽、高与面积
* centroids: 每个组件的中心位置坐标cx, cy
* connectivity: 邻域
* ltype: 输出图深度
* ccltype: 连通组件算法
*********************************************************************/
//ccltype取值
enum ConnectedComponentsTypes {
CC_STAT_LEFT = 0, // 组件的左上角点像素点坐标的X位置
CC_STAT_TOP = 1, // 组件的左上角点像素点坐标的Y位置
CC_STAT_WIDTH = 2, // 组件外接矩形的宽度
CC_STAT_HEIGHT = 3, // 组件外接矩形的高度
CC_STAT_AREA = 4, // 当前连通组件的面积(像素单位)
CC_STAT_MAX = 5 // 最大枚举值,仅在内部用于内存分配(可忽略)
};
综合代码
#include <iostream>
#include <string>
#include <time.h>
#include <map>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
class Connection
{
public:
Connection() :img(imread("text.jpg",IMREAD_GRAYSCALE))//转化为灰度图
{
result["原图"] = img;
threshold(img, result["阈值化"], 200, 255, THRESH_BINARY_INV);
}
//着色
void DrawColor(Mat& src, Mat& result, int nCount)//图像,结果图像,连通区域的个数
{
vector<Vec3b> colors(nCount);
for (int i = 1; i < nCount; i++)
{
colors[i] = Vec3b(rand() % 256, rand() % 256, rand() % 256);
}
//连通区域着色
result = Mat::zeros(img.size(), CV_8UC3);
for (int y = 0; y < img.rows; y++)
{
for (int x = 0; x < img.cols; x++)
{
int label = src.at<int>(y, x);
if (label > 0 && label <= nCount)
{
result.at<Vec3b>(y, x) = colors[label];
}
if (label == 0)
{
result.at<Vec3b>(y, x) = Vec3b(255, 255, 255);
}
}
}
}
void NoCount()
{
Mat temp;
int nCount = connectedComponents(result["阈值化"], temp);
//temp不能直接显示,需要转换后才能显示
DrawColor(temp, result["不统计"], nCount);
}
void Count()
{
Mat stats, center, temp;
int nCount = connectedComponentsWithStats(result["阈值化"], temp, stats, center, 8, CC_STAT_AREA);
DrawColor(temp, result["统计"], nCount);
//利用统计信息标记连通域
for (int i = 1; i < nCount; i++)
{
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);//宽和高
rectangle(result["统计"], Rect(x, y, w, h), Scalar(0, 0, 0), 2);//绘制矩形
}
}
void Show()
{
for (auto& v : result)
{
imshow(v.first, v.second);
}
waitKey(0);
}
protected:
Mat img;
map<string, Mat> result;
};
int main()
{
srand((unsigned int)time(nullptr));
Connection* p = new Connection;
p->NoCount();
p->Count();
p->Show();
return 0;
}