泛洪填充/漫水填充#

泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是

windows paint的油漆桶功能。算法的原理很简单,就是从一个点开始,将附近像素点填充成新

的颜色,直到封闭区域内的所有像素点都被填充新颜色为止。泛红填充实现最常见有四邻域

像素填充法,八邻域像素填充法,基于扫描线的像素填充方法

它的原理其实很接单,我们首先来看一下它的api定义

1
2
3
4
5
6
7
8
9
int cv::floodFill   (   
    InputOutputArray    image, 输入图像
    Point   seedPoint,         种子点位置
    Scalar  newVal,            填充颜色值  
    Rect *  rect = 0,          输出填充区域的boundingbox
    Scalar  loDiff = Scalar(), 颜色的最小差异
    Scalar  upDiff = Scalar(), 颜色的最大差异
    int     flags = FLOODFILL_FIXED_RANGE  常用的是按照颜色差异范围来填充 
)   

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;


Mat src;
RNG rng(123456);

void onMouse(int event, int x, int y, int flags, void* userdata){
    // 如果鼠标左键被按下,就在这里打个点开始填充
    if(event == CV_EVENT_LBUTTONDOWN){
        // 构建一个随机的颜色
        Scalar randomColor = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));

        Rect rect;
        //       输入图  种子点            填充颜色      输出填充大小           最小差异                        最大差异                            按照范围填充
        floodFill(src,Point(x,y),randomColor,&rect,Scalar(20,20,20),Scalar(100,100,100),FLOODFILL_FIXED_RANGE);

        cout<<rect.size()<<endl;

        imshow("src",src);
    }
}

int main(int argc,char** argv){
    string path = "/home/kaijun/Documents/resource/opencv/zp.jpg";
    // 读取图片
    src = imread(path,IMREAD_COLOR);

    // 定义窗口
    namedWindow("src",WINDOW_NORMAL);
    setMouseCallback("src",onMouse);
    imshow("src",src);

    waitKey(0);
    return 0;
}

图像分水岭#

任何灰度图像都可以看做是地形表面,把高强度看做山峰和丘陵,低强度看做山谷。首先,你用不同显色的水(label)充满每个单独的山谷(局部最小值),随着水位的上升,根据山谷附近的山峰高度(梯度),那些来自不同山谷的不同颜色的水开始合并。但是我们要通过在这些合并的位置建立阻隔,来避免合并的发生。

我们不断地加水,并修建阻隔,直到所有的山峰被水覆盖。如此,我们创建的阻隔所分割出的结果即是分水岭其背后的“哲学”。一般用于分离相互接触的对象

OpenCV提供了基于marker的分水岭算法,但是我们需要将确定的背景和前景标记出来。

CMM的分水岭变换示意

下面左边的梯度图,可以描述为右边的地形图,地形的高度是由梯度图的梯度值决定,灰度为0对应地形图的地面,梯度值最大的像素对应地形图的最高点。

下面这张图就是以地形图的角度来看分水岭

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
Created by kaijun on 10/17/19.
 图像分水岭
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

string path = "/home/kaijun/Pictures/wan.png";

Mat src = imread(path,IMREAD_COLOR);
int n=1;
void onMouse(int event, int x, int y, int flags, void* userdata){
    Mat markers = *(Mat*)userdata;
    // 当鼠标左键按下的时候
    if(event == EVENT_LBUTTONDOWN){

        circle(markers,Point(x,y),50,Scalar(n++),-1);

        imshow("markers",markers);
    }

    // 当鼠标右键按下的时候
    if(event == EVENT_RBUTTONDOWN){
        // 颜色列表
        vector<Vec3b> colors={Vec3b(255,255,0),Vec3b(0,255,255)};
        // 执行分水岭操作
        watershed(src,markers);

        // 定义一个用于显示结果的图像
        Mat result(markers.size(),CV_8UC3);
        for (int row = 0; row < markers.rows; ++row) {
            for (int col = 0; col < markers.cols; ++col) {
                int index = markers.at<int>(row,col);
                if(index>0 && index<n){
                    result.at<Vec3b>(row,col) = colors[index-1];
                    cout<<index<<n<<endl;
                }
            }
        }

        imshow("result",result);
    }

}

int main(int argc,char** argv){
    // 显示原图像
    imshow("src",src);

    // 彩色图像转成灰度图像
    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);
    imshow("gray",gray);

    // 定义用于记录标记的图像
    Mat markers(src.size(),CV_32S);
    // 注册鼠标滑动时间
    setMouseCallback("gray",onMouse,&markers);

    Mat binary;
    threshold(gray,binary,0,255,THRESH_BINARY|THRESH_TRIANGLE);
    imshow("binary",binary);

    waitKey(0);
    return 0;
}

距离变换#

Opencv中distanceTransform方法用于计算图像中每一个非零点距离自己最近的零点的距离,方法输出的信息为距离而不是颜色值,图像上越亮的点,代表了离零点的距离越远。

可以用来细化轮廓,或者寻找物体的质心!

方法参数说明:

1
2
3
4
5
6
void cv::distanceTransform(InputArray   src, 输入图像
    OutputArray     dst,   输出图像
    int     distanceType,   计算距离的方法
    int     maskSize,       掩膜的大小,一般为3x3 或者 5x5
    int     dstType = CV_32F 选用默认值即可
)   

distanceType类型介绍

DIST_USER User defined distance.
DIST_L1 distance = |x1-x2| + |y1-y2|
DIST_L2 the simple euclidean distance
DIST_C distance = max(|x1-x2|,|y1-y2|)
DIST_L12 L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
DIST_WELSCH distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
DIST_HUBER distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
距离变换
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char** argv){
    string path = "/home/kaijun/Documents/resource/opencv/img/shape.jpg";
    // 读取原图
    Mat src = imread(path,IMREAD_GRAYSCALE);

    imshow("src",src);
    // 将图片转成二值图像
    Mat binary;
    threshold(src,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);

    imshow("binary",binary);
    cout<<binary.type()<<binary.channels()<<endl;
    // 距离变换
    Mat distanceImg;
    distanceTransform(binary,distanceImg,DIST_L2,3);

    // 将数据归一化到0~1之间
    normalize(distanceImg,distanceImg,0,1.0,NORM_MINMAX);

    imshow("distance",distanceImg);

    waitKey(0);
    return 0;
}

案例#

0. 图片修复#

opencv中给我们提供了一个用于修复图片的函数

1
2
3
4
5
6
7
void inpaint( InputArray src, InputArray inpaintMask,OutputArray dst, double inpaintRadius, int flags );

src : 表示输入的图像
inpaintMask: 掩膜,其实就是要修复哪些区域
dst : 表示修复输出的图像
inpaintRadius: 表示修复半径
flags: 表示修复时所使用的算法,有CV_INPAINT_TELEA和CV_INPAINT_NS可选,处理效果差不多

下面这里有一张我的示例图像,左边为待修复的图像,右边为修复之后的图像

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
图片修复
 */
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

Point prevPt;
Mat src,inpaintMask;

void onMouse(int event, int x, int y, int flags, void* userdata){
    if (event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))
        prevPt = Point(-1, -1);
    else if (event == CV_EVENT_LBUTTONDOWN)
        prevPt = Point(x, y);
    else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
    {
        Point pt(x, y);
        if (prevPt.x < 0)
            prevPt = pt;
        line(inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0);//mask

        // 使用inpaint函数进行图片修复
        inpaint(src, inpaintMask, src, 5, CV_INPAINT_TELEA);

        prevPt = pt;
        imshow("inpaint", src);
    }
}

int main(int argc,char** argv){
    string path = "/home/kaijun/Documents/resource/opencv/img/itheima_inpaint.jpg";
    src = imread(path,IMREAD_COLOR);
    // 定义窗口
    namedWindow("inpaint",WINDOW_NORMAL);
    // 定义鼠标事件
    setMouseCallback("inpaint",onMouse);
    // 显示原图
    imshow("inpaint",src);
    // 定义掩膜
    inpaintMask = Mat(src.size(),CV_8UC1);

    waitKey(0);
    return 0;
}

1. 切边#

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 去除图片多余的背景
*/
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void fetchROI(Mat &img);

int main(){
    string path = "/home/kaijun/Documents/resource/opencv/01_qiebian.jpg";
    Mat src = imread(path,IMREAD_COLOR);
    // 显示图片
    imshow("src",src);
    // 提取感兴趣的区域
    fetchROI(src);

    waitKey(0);
    return 0;
}

void fetchROI(Mat &img){
    // 将彩色图转成灰度图
    Mat gray(img.size(),CV_8UC1);
    cvtColor(img,gray,COLOR_BGR2GRAY);
    imshow("gray",gray);

    // 将图像二值化处理
    Mat binary(img.size(),CV_8UC1);
    threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
    imshow("binary",binary);
    // 查找轮廓
    vector<vector<Vec2i>> contours;
    vector<Vec4i> hierarchy;
    findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);


    RotatedRect resultRect;

    // 遍历轮廓,求轮廓的最大外切矩形
    for(int i = 0; i<contours.size();i++){
        // 取到当前遍历轮廓
        RotatedRect rect = minAreaRect(contours[i]);
        if(rect.size.width > img.size().width*0.75){
            // 绘制轮廓
            //drawContours(img,contours,i,Scalar(0,255,255),2,LINE_AA);
            // 记录外切矩形
            resultRect = rect;
        }
    }

    Mat dst = img(resultRect.boundingRect());

    imshow("dst123",dst);
}

2. 切边+旋转#

示例代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
/**
 去除图片多余的背景,切边并且旋转
*/
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void verifyRotation(Mat &img,Mat &outputImg);
void fetchROI(Mat &img);

int main(){
    string path = "/home/kaijun/Documents/resource/opencv/02_qiebian.jpg";
    Mat src = imread(path,IMREAD_COLOR);
    // 显示图片
    imshow("src",src);

    Mat rotationImg;

    verifyRotation(src,rotationImg);

    fetchROI(rotationImg);

    waitKey(0);
    return 0;
}

/**
 *
 */
void fetchROI(Mat &img){
    // 将彩色图转成灰度图
    Mat gray(img.size(),CV_8UC1);
    cvtColor(img,gray,COLOR_BGR2GRAY);
    imshow("gray",gray);

    // 将图像二值化处理
    Mat binary(img.size(),CV_8UC1);
    threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
    imshow("binary",binary);
    // 查找轮廓
    vector<vector<Vec2i>> contours;
    vector<Vec4i> hierarchy;
    findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);


    RotatedRect resultRect;

    // 遍历轮廓,求轮廓的最大外切矩形
    for(int i = 0; i<contours.size();i++){
        // 取到当前遍历轮廓
        RotatedRect rect = minAreaRect(contours[i]);
        if(rect.size.width > img.size().width*0.75){
            // 绘制轮廓
            //drawContours(img,contours,i,Scalar(0,255,255),2,LINE_AA);
            // 记录外切矩形
            resultRect = rect;
        }
    }

    Mat dst = img(resultRect.boundingRect());

    imshow("dst123",dst);
}


void verifyRotation(Mat &img,Mat &outputImg){
    // 将彩色图转成灰度图
    Mat gray(img.size(),CV_8UC1);
    cvtColor(img,gray,COLOR_BGR2GRAY);
    imshow("gray1",gray);

    // 将图像二值化处理
    Mat binary(img.size(),CV_8UC1);
    threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
    imshow("binary1",binary);
    // 查找轮廓
    vector<vector<Vec2i>> contours;
    vector<Vec4i> hierarchy;
    findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);


    RotatedRect resultRect;

    // 遍历轮廓,求轮廓的最大外切矩形
    for(int i = 0; i<contours.size();i++){
        // 取到当前遍历轮廓
        RotatedRect rect = minAreaRect(contours[i]);
        if(rect.size.width > img.size().width*0.75){
            // 绘制轮廓
            //drawContours(img,contours,i,Scalar(255,0,255),2,LINE_AA);
            // 记录外切矩形
            resultRect = rect;
        }
    }

    // 打印图片需要旋转的角度
    cout<<resultRect.angle<<endl;

    // 先对图片进行旋转
    Point2f center(img.cols/2,img.rows/2);
    Mat matrix = getRotationMatrix2D(center,(resultRect.angle)+90,1);

    warpAffine(img,outputImg,matrix,img.size());

    imshow("rotateimg",outputImg);
}

3. 直线检测#

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 检测填空题中的直线: 采用形态学方式
*/
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

Mat src,result;
int threshold_value=95;
int max_count=255;

void detectline(int,void*);

int main(){
    string path = "/home/kaijun/Documents/resource/opencv/engline.jpg";

    src = imread(path,IMREAD_COLOR);
    imshow("src0",src);
    detectline();
    createTrackbar("threshold1","src",&threshold_value,max_count,detectline);

    waitKey(0);
    return 0;
}

void detectline(int ,void*){

    // 将彩色图转成灰度图
    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    // 1. 将图像进行二值化处理
    Mat binary;
    threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
    imshow("binary",binary);

    // 2. 形态学操作
    Mat morph;
    Mat kernel = getStructuringElement(MORPH_RECT, Size(20, 1));
    morphologyEx(binary,morph,MORPH_OPEN,kernel);
    imshow("morphology",morph);

    // 3. 膨胀一下
    Mat kernel2 = getStructuringElement(MORPH_RECT,Size(3,3));
    dilate(morph,morph,kernel2);
    imshow("morphology2",morph);

    // 4. 寻找霍夫直线
    vector<Vec4i> lines;
    HoughLinesP(morph,lines,1,CV_PI/180,30,20,0);

    for(int i=0; i < lines.size();i++){
        Vec4i ln = lines[i];
        line(src,Point(ln[0],ln[1]),Point(ln[2],ln[3]),Scalar(0,0,255),1);
    }

    imshow("src",src);
}

4. 图像计数#

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
    统计零件的数量
*/
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(){
    // 图片路径
    string path = "/home/kaijun/Pictures/lingjian.png";
    //1. 读取图片
    Mat src = imread(path,IMREAD_COLOR);
    imshow("src",src);
    //2. 将图片转成灰度
    Mat gray;
    cvtColor(src,gray,COLOR_BGR2GRAY);

    // 将灰度图转成而制图,并且去掉背景
    Mat binary;
    threshold(gray,binary,0,255,THRESH_BINARY|THRESH_TRIANGLE);
    imshow("no background",binary);

    // 距离变换
    Mat dist;
    distanceTransform(binary,dist,DIST_L2,3);
    normalize(dist, dist, 0, 1.0, NORM_MINMAX);
    imshow("dist",dist);

    // 将距离变换的结果,通过阈值让图像粘连断开
    threshold(dist, dist, 0.5, 1.0, THRESH_BINARY);
    imshow("dist2",dist);

    // 统计图像的轮廓
    vector<vector<Vec2i>> contours;
    vector<Vec4i> hierarchy;

    cout<<dist.type()<<endl;
    dist.convertTo(dist,CV_8UC1);
    findContours(dist,contours,hierarchy,RETR_LIST,CHAIN_APPROX_NONE);

    cout<<contours.size()<<endl;

    // 绘制轮廓
    RNG rng(123456);
    Mat result(dist.size(),CV_8UC3);
    for (int i = 0; i < contours.size(); ++i) {
    drawContours(result,contours,i,Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)),-1);
    }

    imshow("result",result);
    waitKey(0);
    return 0;
}

5. 图像分割#

6. 证件照换背景#

K-均值聚类算法是著名的划分聚类分割方法。划分方法的基本思想是:给定一个有N个元组或者纪录的数据集,分裂法将构造K个分组,每一个分组就代表一个聚类,K<N。而且这K个分组满足下列条件:(1) 每一个分组至少包含一个数据纪录;(2)每一个数据纪录属于且仅属于一个分组;对于给定的K,算法首先给出一个初始的分组方法,以后通过反复迭代的方法改变分组,使得每一次改进之后的分组方案都较前一次好,而所谓好的标准就是:同一分组中的记录越近越好,而不同分组中的纪录越远越好。

K-means算法的工作原理:算法首先随机从数据集中选取 K个点作为初始聚类中心,然后计算各个样本到聚类中心的距离,把样本归到离它最近的那个聚类中心所在的类。计算新形成的每一个聚类的数据对象的平均值来得到新的聚类中心,如果相邻两次的聚类中心没有任何变化,说明样本调整结束,聚类准则函数 已经收敛。本算法的一个特点是在每次迭代中都要考察每个样本的分类是否正确。若不正确,就要调整,在全部样本调整完后,再修改聚类中心,进入下一次迭代。这个过程将不断重复直到满足某个终止条件,终止条件可以是以下任何一个:

(1)没有对象被重新分配给不同的聚类。

(2)聚类中心不再发生变化。

(3)误差平方和局部最小。

K-means聚类算法的一般步骤:

(1)从 n个数据对象任意选择 k 个对象作为初始聚类中心;

(2)循环(3)到(4)直到每个聚类不再发生变化为止;

(3)根据每个聚类对象的均值(中心对象),计算每个对象与这些中心对象的距离;并根据最小距离重新对相应对象进行划分;

(4)重新计算每个(有变化)聚类的均值(中心对象),直到聚类中心不再变化。这种划分使得下式最小

K-均值聚类法的缺点:

(1)在 K-means 算法中 K 是事先给定的,这个 K 值的选定是非常难以估计的。

(2)在 K-means 算法中,首先需要根据初始聚类中心来确定一个初始划分,然后对初始划分进行优化。

(3) K-means算法需要不断地进行样本分类调整不断地计算调整后的新的聚类中心因此当数据量非常大时算法的时间开销是非常大的。

(4)K-means算法对一些离散点和初始k值敏感,不同的距离初始值对同样的数据样本可能得到不同的结果。

kmeans动态演示:https://www.naftaliharris.com/blog/visualizing-k-means-clustering/

kmeans动态演示:http://shabal.in/visuals/kmeans/4.html

示例代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

Scalar colors[]={
        Scalar(255,0,0),
        Scalar(255,255,0),
        Scalar(0,0,255),
        Scalar(255,0,255),
        Scalar(0,255,0),

};

void useKmeans(Mat &src, Mat &distImg);
void changeBackground(Mat&src ,Mat &img,Vec3b color);
int main(int argc, char** argv){
    //string path = "/home/kaijun/Documents/resource/opencv/zhengjianzhao.png";
    //string path = "/home/kaijun/Pictures/zjz.jpeg";
    string path = "/home/kaijun/Pictures/zjz3.jpg";
    // 读取原图
    Mat src = imread(path);
    // 显示原图像
    imshow("src",src);

    // 创建一张空白的图片, 用于接收kmeans聚类结果
    Mat distImg(src.size(),src.type());
    // 使用聚类算法 对图像进行处理
    useKmeans(src, distImg);

    changeBackground(src,distImg,Vec3b(125,125,120));

    waitKey(0);

    return 0;
}

/**
 * 更换背景
 * @param img
 * @param result
 */
void changeBackground(Mat &src, Mat &img,Vec3b color){
    // 1. 提取到背景颜色值
    Vec3b backgroundColor = img.at<Vec3b>(0,0);

    // 2. 遍历背景,创建一个蒙版
    Mat mask(img.size(),CV_8UC1);
    for (int row = 0; row < img.rows; ++row) {
        for (int col = 0; col < img.cols; ++col) {
            Vec3b currentColor = img.at<Vec3b>(row,col);
            if(currentColor == backgroundColor){
                mask.at<uchar>(row,col) = 0;
            }else{
                mask.at<uchar>(row,col) = 255;
            }
        }
    }

    // 3. 形态学变换,去掉一些细小的边缘
    erode(mask,mask,Mat::ones(3,3,CV_8UC1),Point(-1,-1),3);

    // 创建一个蒙版
    imshow("mask",mask);

    // 5. 从原图中抠出人像
    Mat copyImg;
    src.copyTo(copyImg,mask);

    imshow("copy",copyImg);
    // 6. 遍历抠出来的人像
    for (int row = 0; row < copyImg.rows; ++row) {
        for (int col = 0; col < copyImg.cols; ++col) {
            int value = mask.at<uchar>(row,col);
            if(value == 0){
                copyImg.at<Vec3b>(row,col)=Vec3b(125,125,125);
            }
        }
    }
    imshow("result",copyImg);
}


void useKmeans(Mat &src, Mat &distImg) {// 封装数据点
    int sampleCount = src.rows*src.cols;
    // N 行 3列数据
    Mat sampleData(sampleCount,src.channels(),CV_32F);
    // 将图片的像素数据封装到样本数据中
    for(int row = 0; row< src.rows;row++){
        for(int col=0; col<src.cols;col++){
            int index = row*src.cols + col;
            // 从原图从取出颜色信息
            Vec3b bgr = src.at<Vec3b>(row,col);
            // 将数据填到数据列表中
            sampleData.at<Vec3f>(index)=bgr;
        }
    }

    // 调用kmeans函数
    int clusterNum = 4;
    Mat labels;
    TermCriteria termCriteria(TermCriteria::EPS|TermCriteria::COUNT,10,0.1);
    Mat centers;
    kmeans(sampleData,clusterNum,labels,termCriteria,3,KMEANS_PP_CENTERS,centers);

    //取出每一个样本数据,根据它的标签 填入颜色
    for (int row = 0; row < src.rows; ++row) {
        for (int col = 0; col < src.cols; ++col) {
            int index = row*src.cols + col;

            int label = labels.at<int>(index,0);

            distImg.at<Vec3b>(row,col)[0] = colors[label][0];
            distImg.at<Vec3b>(row,col)[1] = colors[label][1];
            distImg.at<Vec3b>(row,col)[2] = colors[label][2];
        }
    }

    imshow("kmeans",distImg);
}