前言
图像分割就是预测图像中每一个像素所属的类别或者物体。图像分割有两个子问题,一个是只预测类别层面的分割,对每个像素标出一个位置。第二个是区分不同物体的个体。应用场景,比如自动驾驶,3D 地图重建,美化图片,人脸建模等等。 传统的图像分割通常和图像分类结合,它是图像识别的第一阶段。
图1 图像识别流程
1、图像分割难点:
噪声影响
无法控制环境
没有一致的标准
没有足够的检测数据
存在病态问题
当图像背景中存在与前景目标相 同或相似区域时,没有用户的交互,自动分割出感兴趣的前景目标这个问题本身就是病态的。 图像分割可以分为两类:
完全分割
每部分都和一个实物相关
没有重叠的区域
部分分割
拥有均匀亮度、颜色等的区域
重叠的部分,需要进一步处理
2、分割方法
(1)基于阈值的分割
阈值分割是基于直方图的,对图像进行灰度阈值化是最简单的分割处理。图像阈值化算法简单高效,在很多场景中依然得到很多应用,实时性很好。图像阈值化的缺陷也是明显的,不能够很好的利用图像中的诸如色彩、纹理等语义信息,因此在复杂场景中无法得到目标结果。
图像阈值化分为全局阈值和局部阈值及动态阈值。全局阈值是对整幅图像使用单个阈值,局部阈值是根据图像局部信息在局部执行阈值化。阈值化操作有许多改进算法,例如:局部阈值化、带阈值化、半阈值化、多阈值化等。阈值化的关键在于如何选择阈值。
阈值分割的优点是计算简单、运算效率较高、速度快。全局阈值对于灰度相差很大的不同目标和背景能进行有效的分割。当图像的灰度差异不明显或不同目标的灰度值范围有重叠时,应采用局部阈值或动态阈值分割法。另一方面,这种方法只考虑像素本身的灰度值,一般不考虑空间特征,因而对噪声很敏感。在实际应用中,阈值法通常与其他方法结合使用。
全局阈值
全局阈值法采用同一个灰度值作为分割门限对整幅图进行处理,特别对直方图分布呈双峰态的图像分割效果好,如:
但在有意义的全局阈值不存在的情况下,全局阈值的分割效果很差,如:
迭代阈值图像分割
迭代阈值的步骤为:
统计图像灰度直方图,求出图象的最大灰度值和最小灰度值,分别记为和,令初始阈值。
根据阈值TK将图象分割为前景和背景,计算小于TO所有灰度的均值ZO,和大于TO的所有灰度的均值ZB。
求出新阈值TK+1=(ZO+ZB)/2;
若TK==TK+1,则所得即为阈值;否则转2,迭代计算。
动态阈值
(2)边缘检测
边缘检测算法是指利用灰度值的不连续性质,以灰度突变为基础分割出目标区域。对铝铸件表面进行成像后会产生一些带缺陷的区域,这些区域的灰度值比较低,与背景图像相比在灰度上会有突变,这是由于这些区域对光线产生散射所引起的。因此边缘检测算子可以用来对特征的提取。
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat roberts(Mat srcImage);
int main(int argc, char** argv)
{
Mat src,src_binary,src_gray;
src = imread("D:img.jpg");
imshow("原图", src);
cvtColor(src, src_gray, COLOR_BGR2GRAY);
GaussianBlur(src_gray, src_binary, Size(3, 3),0, 0, BORDER_DEFAULT);
Mat dstImage = roberts(src_binary);
imshow("dstImage", dstImage);
waitKey(0);
return 0;
}
//roberts 边缘检测
Mat roberts(Mat srcImage)
{
Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows - 1; i++) {
for (int j = 0; j < nCols - 1; j++) {
//根据公式计算
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1))*
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1));
int t2 = (srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1))*
(srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1));
//计算g(x,y)
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
(3)基于颜色空间的分割
在最常见的颜色空间RGB(红、绿、蓝)中,颜色以其红、绿、蓝三种成分表示。在更专业的术语中,RGB将颜色描述为三个成分的元组。每个组件可以取0到255之间的值,其中元组(0,0,0)表示黑色,(255,255,255)表示白色。
RGB是五种主要颜色空间模型之一,每种模型都有许多分支。有这么多颜色空间,因为不同的颜色空间对于不同的目的是有用的。
在印刷领域,CMYK非常有用,因为它描述了从白色背景产生颜色所需的颜色组合。RGB中的0元组是黑色的,而CMYK中的0元组是白色的。我们的打印机包含青色、品红色、黄色和黑色墨盒。
在某些类型的医疗领域,装有染色组织样本的载玻片被扫描并保存为图像。它们可以在HED空间中进行分析,HED空间是应用于原始组织的染色类型——苏木精、曙红和DAB——饱和度的表示。
HSV和HSL是色调、饱和度和亮度的描述,对于识别图像中的对比度特别有用。这些颜色空间经常用于软件和网页设计中的颜色选择工具。
实际上,颜色是一个连续的现象,意味着有无限多的颜色。然而,颜色空间通过离散结构(固定数量的整数数值)来表示颜色,这是可以接受的,因为人眼和感知也是有限的。颜色空间完全能够代表我们能够区分的所有颜色。
3、问题引申
若通过上述的分割方法,得到了一幅图像的分割结果,如下图所示:
这里我用不同颜色标记不同的分割体。我想得到该图像的边缘,类似下图,请问有什么好的方法。
cv.findContours的输入只能是二值影像,这样并不能进行边缘的提取。请问有没有小伙伴有比较好的方法呢?