目录
0x01 FloodFill分割
FloodFill泛洪填充算法是在 很多图形绘制软件中常用的填充算法,通常来说是自动选中与种子像素相关的区域,利用指定的颜色进行区域颜色替换,可用于标记或分离图形的某些部分。比如windows系统中的图像编辑软件中的油漆桶这一功能,或者是Photoshop的魔术棒选择工具,都是通过FloorFill泛洪填充来改进和延伸的。
(一)原理
从一个点开始遍历附近的像素点,填充成新的颜色,直到封闭区域内的所有像素点都被填充成新颜色为止。
(二)常见方法
4邻域像素填充法、8邻域像素填充法、基于扫描线的像素填充方法等。
(三)OpenCV中的接口
int floodFill( InputOutputArray image, //1或3通道,8bit或浮点型数据,输入图像将会因为被函数操作而修改,如果想保留原图则使用CV_FLOODFILL_MASK_ONLY
Point seedPoint, //种子点开始位置
Scalar newVal, //新的重新绘制的像素值
CV_OUT Rect* rect = 0,
Scalar loDiff = Scalar(), //当前观察像素值与其部件领域像素或者待加入该部件的种子像素之正差的最大值
Scalar upDiff = Scalar(), //当前观察像素值与其部件领域像素或待加入该部件的种子像素之正差的最大值
int flags = 4 ); //操作选项标志位,低位比特包含连通值,4或8
函数解析:
用指定颜色填充一个连通域。高位比特位可以选择0或下面的开关选项的组合:如果设置 CV_FLOODFILL_FIXED_RANGE为当前选项,则考虑当前像素与种子像素的差,否则考虑当前像素与其相邻像素的差。如果设置 CV_FLOODFILL_MASK_ONLY为当前选项,函数不填充原始图像,但填充掩模图像(这种情况下MASK必须是非空的)。
怎么用:
FloodFill区域填充适用于对内定义区域的种子进行填充,区域内部所有像素具有统一的颜色或亮度,外部区域外的所有像素值表现出不同的特征。利用FloodFill可将该区域中的全部像素都设置为另外一个新值,并通过特定规则来实现区域内点中的连通域,从而实现对相似区域进行填充,直到找到区域内所有像素或边界轮廓。
那么我们可以通过实现一个点击一个点,判断邻域的特征值进行填充,就像油漆桶的操作:
#include
#include
#include
#include
#include "opencv2/core/core.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
#include
using namespace cv;
using namespace std;
// 初识化参数
Mat image, gray, mask;
int ffillMode = 1;
int loDiff = 20, upDiff = 20;
int connectivity = 4;
int isColor = true;
bool useMask = false;
int newMaskVal = 255;
// 鼠标响应函数
static void onMouse(int event, int x, int y, int, void*)
{
if (event != EVENT_LBUTTONDOWN)
return;
// floodfill参数设置
Point seed = Point(x, y);
int lo = ffillMode == 0 ? 0 : loDiff;
int up = ffillMode == 0 ? 0 : upDiff;
//设置flag,为什么要这么写?
int flags = connectivity + (newMaskVal << 8) +
(ffillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);
// 颜色分量随机选取
int b = (unsigned)theRNG() & 255;
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
Rect ccomp;
// 颜色选择,判断是否要显示为灰度
Scalar newVal = isColor ? Scalar(b, g, r)
: Scalar(r * 0.299 + g * 0.587 + b * 0.114);
Mat dst = isColor ? image : gray;
int area;
// 根据标志位选择泛洪填充
if (useMask)
{
// 阈值化操作
threshold(mask, mask, 1, 128, CV_THRESH_BINARY);
cout << lo << up << endl;
area = floodFill(dst, mask, seed,
newVal, &ccomp, Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
imshow("mask", mask);
}
else
{
// 泛洪填充
// 判断点击点附近的值,进行填充
area = floodFill(dst, seed, newVal, &ccomp,
Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
}
imshow("image", dst);
}
int main()
{
cv::Mat srcImage = cv::imread("./image/circle.jpg");
if (srcImage.empty())
return 0;
srcImage.copyTo(image);
cvtColor(srcImage, gray, CV_BGR2GRAY);
mask.create(srcImage.rows + 2, srcImage.cols + 2, CV_8UC1);
// 鼠标响应回调函数
namedWindow("image", 0);
setMouseCallback("image", onMouse, 0);
for (;;)
{
imshow("image", isColor ? image : gray);
int c = waitKey(0);
if ((c & 255) == 27)
{
cout << "Exiting ...\n";
break;
}
if (c == 'r')
{
cout << "Original image is restored\n";
srcImage.copyTo(image);
cvtColor(image, gray, CV_BGR2GRAY);
mask = Scalar::all(0);
}
}
return 0;
}
最后实现的效果如下:
0x02 均值漂移MeanShift
均值漂移是一种 核密度估计方法,用来分析复杂多模特征空间。本质其实就是利用了梯度下降法,沿着 梯度下降方法寻找目标函数的极值。图像分割是找到分割像素点所属类的中心,均值漂移认为类中心是概率密度的极大值点,对于任一像素沿着梯度方向总能找到其极值点。其实这个技术也在神经网络中运用,在不断的梯度下降中找到合适的权重。
均值漂移的步骤:
(1)平滑mask点搜索。图像由N维像素构成特征空间,特征空间由 坐标空间与 颜色空间构成,空间域或颜色域构成相应的联合域。
那么对于图像中的像素点,它在联合特征空间中迭代搜索相应的mask点,设置源图像的像素用xi表示,平滑后的图像用zi表示,平滑是将模点的颜色值初始化为原始像素点,利用空间颜色域特征函数yi+k+1得到平滑后的像素值:
那么其中g为核函数,hr为颜色空间的核平滑尺度,xn为特征函数的空间分布。对于每一个像素点x,我们计算其y,然后在坐标空间内应用方形区域筛选靠近yi+k的数据点,然后对得到的数据点计算其重心移动yi+k+1,接着判断得到的点集是否满足终止条件,最后将得到mask点的颜色更新xi=yi,c。
(2)mask点聚类。对平滑后的图像应用颜色相似度与空间位置相似性进行判定,在实际计算中常常只考虑颜色相似性,对满足||zi-zj||
Original: https://blog.csdn.net/Alkaid2000/article/details/124677761
Author: 郑烯烃快去学习
Title: OpenCV入门(十)——图像分割技术
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/532443/
转载文章受原作者版权保护。转载请注明原作者出处!