图像分割-区域分裂合并算法

原文:https://www.jianshu.com/p/fa573431ef3d
区域分裂合并法:区域分裂合并法是一种图像分割算法。 它与区域生长法略有相似之处,但无需预先指定种子点,而是按某种一致性准则分裂或者合并区域。
分裂合并法对分割复杂的场景图像比较有效。
算法的思想并不复杂,总的来说,就是先把图像分成4块,若这其中的一块符合分裂条件,那么这一块又分裂成4块,就这样一直分裂。分裂到一定数量时,以每块为中心,检查相邻的各块,满足一定条件,就合并。
如此循环往复进行分裂和合并的操作。
最后合并小区,即把一些小块图像的合并到旁边的大块里。

运算流程:可以先进行分裂运算,然后再进行合并运算;也可以分裂和合并运算同时进行,经过连续的分裂和合并,最后得到图像的精确分割效果。

具体实现时,分裂合并算法是基于四叉树数据表示方式进行。
在这里插入图片描述
该算法难点在于分裂与合并的准则并不好判断,分裂的准则又称为均匀性测试准则,用于判断该块图像是否需要分裂。
最初使用每块图像区域中极大与极小灰度值之差是否在允许的偏差范围来作为均匀性测试准则。 后来均匀性测试准则又被不断的发展。目前,统计检验,比如均方误差最小、F检测等都是最常用的均匀性测试准则。

介绍一下均方误差最小准则:
先计算平均值:C是区域 Ω 中N个点的平均值,f(x,y)可以理解为区域 Ω 中点(x,y)的灰度值。
在这里插入图片描述
有了平均值后计算均方差:
在这里插入图片描述
当然还要设定一个阈值,判断均方差大于一定值时就进行分裂。

选用合适的均匀性测试准则对于提高图像分割质量十分重要,当均匀性测试准则选择不当时,很容易会引起“方块效应”。

我感觉这个算法思想很棒,不用像区域生长一样还要选个“种子点”,而且不是特别复杂,就花一天的时间自己手写代码实现了一下。
但是发现自己写的程序效果并不尽如人意,总结了一下原因,一个是分裂的准则不好选取,另一个是只凭借灰度值来判断条件太少,如果能加上纹理之类的属性就好了。
而且我这个程序合并图像块的时间长了点,还需继续优化。感觉还是得上聚类、K-means之类的机器学习算法,甚至深度学习才是终极的图像分割之道。
原图
图像分裂的效果图
分裂后合并的效果图
我看到网上有人使用递归来进行分裂,这种操作我不太会…… 于是就用了2个vector容器,2个vector轮流操作,一个vector把元素压入另一个vector,同时删除自己的元素,如此循环。

my_Image_Process.h

#ifndef __my_Image_Process_h__
#define __my_Image_Process_h__

#include<opencv2\opencv.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui/highgui.hpp>
#include<opencv2\core\core.hpp>
#include<iostream>
#include<fstream>
#include<string>
#include<cmath>


class My_Image_Processing
{
public:
    My_Image_Processing(Mat inputImg); //构造函数
    My_Image_Processing(My_Image_Processing &imageProcessing); //复制构造函数
    ~My_Image_Processing(); //析构函数
    Mat get_Split_Merge();
    

private:
    int finshSplitFlag = 0;
    Mat srcImg;
    Mat drawImg;
    Mat grayImg;
    Mat drawResultImg;

    vector<Rect> ouRectVector, jiRectVector;

    bool decideSplit(Mat srcImg);
    bool decideMerge(Mat Img_1, Mat Img_2);

    void ouToJiSplit(); //偶数矩形vector分裂后移入奇数矩形vector
    void jiToOuSplit(); //奇数矩形vector分裂后移入偶数矩形vector

    bool twoRectNeighbor(Rect midRect,Rect nearRect); //判断两个矩形是否为相邻的

    void mergeImg(vector<Rect> rectVector);
    Mat drawRect(Mat Img, Rect rect, int color);

};

#endif 

my_Image_Process.cpp

#include"my_Image_Process.h"

using namespace std;
using namespace cv;


My_Image_Processing::My_Image_Processing(Mat inputImg) //构造函数
{
    srcImg = inputImg;
}

My_Image_Processing::My_Image_Processing(My_Image_Processing &imageProcessing) //复制构造函数
{
    srcImg = imageProcessing.srcImg;
}

My_Image_Processing::~My_Image_Processing() //析构函数
{

}

void My_Image_Processing::ouToJiSplit() //偶数矩形vector分裂后移入奇数矩形vector
{
    int width, height;
    int x, y;
    int splitFlag = 0;

    for (int i = 0; i < ouRectVector.size(); i++)
    {
        //只有满足分裂准则,并且面积>=8时才进行分裂
        if (decideSplit(grayImg(ouRectVector[i])) && ouRectVector[i].area() >= 6) //8
        {
            x = ouRectVector[i].x;
            y = ouRectVector[i].y;
            width = ouRectVector[i].width;
            height = ouRectVector[i].height;

            jiRectVector.push_back(Rect(x + 0, y + 0, width / 2, height / 2));
            jiRectVector.push_back(Rect(x + width / 2, y + 0, width - width / 2, height / 2));
            jiRectVector.push_back(Rect(x + 0, y + height / 2, width / 2, height - height / 2));
            jiRectVector.push_back(Rect(x + width / 2, y + height / 2, width - width / 2, height - height / 2));

            //画分割线
            for (int i = 0; i < width; i++)
                drawImg.at<uchar>(y + height / 2, x + i) = 255;
            for (int i = 0; i < height; i++)
                drawImg.at<uchar>(y + i, x + width / 2) = 255;

            splitFlag = 1;
        }
        else
        {
            jiRectVector.push_back(ouRectVector[i]);
        }
    }

    if (!splitFlag) //如果一次都没有进行分裂,则说明到了结束分裂的时候了
        finshSplitFlag = 1;

    //ouRectVector到jiRectVector转换完毕后清空ouRectVector
    while (!ouRectVector.empty())
        ouRectVector.pop_back();


}

void My_Image_Processing::jiToOuSplit() //奇数矩形vector分裂后移入偶数矩形vector
{
    int width, height;
    int x, y;
    int splitFlag = 0;

    for (int i = 0; i < jiRectVector.size(); i++)
    {
        //只有满足分裂准则,并且面积>=8时才进行分裂
        if (decideSplit(grayImg(jiRectVector[i])) && jiRectVector[i].area() >= 6) //8
        {
            x = jiRectVector[i].x;
            y = jiRectVector[i].y;
            width = jiRectVector[i].width;
            height = jiRectVector[i].height;

            ouRectVector.push_back(Rect(x + 0, y + 0, width / 2, height / 2));
            ouRectVector.push_back(Rect(x + width / 2, y + 0, width - width / 2, height / 2));
            ouRectVector.push_back(Rect(x + 0, y + height / 2, width / 2, height - height / 2));
            ouRectVector.push_back(Rect(x + width / 2, y + height / 2, width - width / 2, height - height / 2));

            //画分割线
            for (int i = 0; i < width; i++)
                drawImg.at<uchar>(y + height / 2, x + i) = 255;
            for (int i = 0; i < height; i++)
                drawImg.at<uchar>(y + i, x + width / 2) = 255;

            splitFlag = 1;
        }
        else
        {
            ouRectVector.push_back(jiRectVector[i]);
        }
    }

    if (!splitFlag) //如果一次都没有进行分裂,则说明到了结束分裂的时候了
        finshSplitFlag = 1;

    //jiRectVector到ouRectVector转换完毕后清空jiRectVector
    while (!jiRectVector.empty()) //注意不能使用 for(i=0;i<jiRectVector.size();i++) jiRectVector.pop_back(); 的方法,因为size()会不断减小
        jiRectVector.pop_back();
}

Mat My_Image_Processing::get_Split_Merge()
{
    Mat outImg;

    cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    grayImg.copyTo(drawImg);

    imshow("gray image", grayImg);

    int Count = 0;

    ouRectVector.push_back(Rect(0, 0, grayImg.cols, grayImg.rows)); //先将总图像送入ouRectVector

    while (!finshSplitFlag /* Count <= 10*/)
    {
        if (Count % 2 == 0) //若Count为偶数,则从 ouMatVector 分裂图像,送入 jiMatVector,同时ouMatVector清空栈
        {
            ouToJiSplit(); //偶到奇的分裂
        }
        else
        {
            jiToOuSplit(); //奇到偶的分裂
        }

        Count++;
    }


    imshow("draw image", drawImg);

    cout << "分裂次数为" << Count << endl;

    if (Count % 2 == 1)
    {
        cout << "偶到奇  " << endl;
        cout << "偶的size为" << ouRectVector.size() << endl;
        cout << "奇的size为" << jiRectVector.size() << endl;

        //  for (int i = 0; i < jiRectVector.size(); i++)
        //      imshow(to_string(i), grayImg(jiRectVector[i]));
    }
    else
    {
        cout << "奇到偶  " << endl;
        cout << "偶的size为" << ouRectVector.size() << endl;
        cout << "奇的size为" << jiRectVector.size() << endl;

        //  for (int i = 0; i < ouRectVector.size(); i++)
        //      imshow(to_string(i), grayImg(ouRectVector[i]));
    }

    drawImg.copyTo(drawResultImg);

    for (int i = 0; i < drawResultImg.rows; i++)
        for (int j = 0; j < drawResultImg.cols; j++)
            drawResultImg.at<uchar>(i, j) = 1;

    if (Count % 2 == 0)
        mergeImg(ouRectVector);
    else
        mergeImg(jiRectVector);

    imshow("result", drawResultImg);

    return outImg;
}

//在图像中为矩形填充颜色
Mat My_Image_Processing::drawRect(Mat Img, Rect rect, int color)
{
    for (int i = rect.y; i <= rect.y + rect.height - 1; i++)
    for (int j = rect.x; j <= rect.x + rect.width - 1; j++)
        Img.at<uchar>(i, j) = color;

    return Img;
}

void My_Image_Processing::mergeImg(vector<Rect> rectVector)
{
    int finshMergeFlag = 0;
    int mergeCount = 0;

    int color = 5;


    while (rectVector.size()>2) //若已经合并到了一定数量  
    {
        Rect nowRect = rectVector.front(); //得到rectVector中的第一个元素,其下标为0  rectVector.front()

        if (drawResultImg.at<uchar>(nowRect.tl().y, nowRect.tl().x) == 1) //若该矩形仍是原色
            color = (color + 30) < 255 ? (color + 30) : 255;
        else
            color = drawResultImg.at<uchar>(nowRect.y, nowRect.x); //若该矩阵不为原色,取该矩阵的颜色


        drawResultImg = drawRect(drawResultImg, nowRect, color);

        if (rectVector.size() >= 2)
        {
            for (int i = 1; i < rectVector.size(); i++) //从第二个元素开始遍历
            {
                if (twoRectNeighbor(nowRect, rectVector[i]) && decideMerge(grayImg(nowRect), grayImg(rectVector[i])))
                {

                    if (drawResultImg.at<uchar>(rectVector[i].tl().y, rectVector[i].tl().x) != 1)
                        color = drawResultImg.at<uchar>(rectVector[i].y, rectVector[i].x);

                    drawResultImg = drawRect(drawResultImg, rectVector[i], color);

                }
            }
        }

        rectVector.erase(rectVector.begin()); //删掉遍历后的第一个元素
        mergeCount++;
    }
    cout << "总次数为: " << mergeCount << endl;
    cout << "剩余的数量为" << rectVector.size() << endl;



}


bool My_Image_Processing::decideSplit(Mat srcImg)
{
    int i = 0, j = 0;
    int maxValue = 0, minValue = 1000;
    bool flag;

    for (i = 0; i < srcImg.rows; i++)
    {
        for (j = 0; j < srcImg.cols; j++)
        {
            if (srcImg.at<uchar>(i, j)>maxValue) //取出最大值
                maxValue = srcImg.at<uchar>(i, j);

            if (srcImg.at<uchar>(i, j) < minValue) //取出最小值
                minValue = srcImg.at<uchar>(i, j);
        }
    }

    if (maxValue - minValue>40)  //30
        flag = 1;
    else
        flag = 0;

    return flag;
}

bool My_Image_Processing::decideMerge(Mat Img_1, Mat Img_2)
{
    int i = 0, j = 0;
    int maxValue = 0, minValue = 1000;
    int Sum_1 = 0, Sum_2 = 0;
    int count_1 = 0, count_2 = 0;
    bool flag;

    for (i = 0; i < Img_1.rows; i++)
    {
        for (j = 0; j < Img_1.cols; j++)
        {
            Sum_1 = Sum_1 + Img_1.at<uchar>(i, j);
            count_1++;
        }
    }
    Sum_1 /= count_1; //计算Img_1的平均值

    for (i = 0; i < Img_2.rows; i++)
    {
        for (j = 0; j < Img_2.cols; j++)
        {
            Sum_2 = Sum_2 + Img_2.at<uchar>(i, j);
            count_2++;
        }
    }
    Sum_2 /= count_2; //计算Img_2的平均值

    if (abs(Sum_1 - Sum_2)<5) //若平均值之差小于20,满足条件,可以合并
        flag = true;
    else
        flag = false;

    return flag;
}

bool My_Image_Processing::twoRectNeighbor(Rect midRect, Rect nearRect) //判断两个矩形是否为相邻的
{
    bool flag = false;

    Rect bigMidRect = midRect;

    //将矩形四条边都扩大1
    bigMidRect.x -= 1;
    bigMidRect.y -= 1;
    bigMidRect.width += 2;
    bigMidRect.height += 2;

    for (int i = nearRect.tl().x; i < nearRect.br().x; i++)
    {
        if (bigMidRect.contains(Point(i, nearRect.tl().y)) || bigMidRect.contains(Point(i, nearRect.br().y)))
        {
            return true;
        }
    }

    for (int i = nearRect.tl().y; i < nearRect.br().y; i++)
    {
        if (bigMidRect.contains(Point(nearRect.tl().x, i)) || bigMidRect.contains(Point(nearRect.br().x, i)))
        {
            return true;
        }
    }


    return false;
}
  • 11
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是基于区域分裂合并图像分割MATLAB代码: ```matlab % 读取图像 I = imread('image.jpg'); figure;imshow(I);title('原始图像'); % 将图像转化为灰度图像 Igray = rgb2gray(I); % 初始化区域 labels = zeros(size(Igray)); regionNum = 1; % 初始化分割参数 minRegionSize = 100; maxRegionSize = 500; threshold = 10; % 区域分割 while (1) % 计算每个区域的平均灰度值 regionMeans = zeros(regionNum, 1); for i = 1:regionNum [r, c] = find(labels == i); regionMeans(i) = mean(Igray(sub2ind(size(Igray), r, c))); end % 计算每个区域与相邻区域的平均灰度值差 regionDiffs = zeros(regionNum, regionNum); for i = 1:regionNum-1 for j = i+1:regionNum [r_i, c_i] = find(labels == i); [r_j, c_j] = find(labels == j); dist = pdist2([mean(r_i), mean(c_i)], [mean(r_j), mean(c_j)]); if (dist <= sqrt(2)) regionDiffs(i,j) = abs(regionMeans(i) - regionMeans(j)); end end end % 找到最大的平均灰度值差 maxDiff = max(max(regionDiffs)); if (maxDiff <= threshold) break; end % 找到差值最大的区域并进行分裂 [region1, region2] = find(regionDiffs == maxDiff); [r_1, c_1] = find(labels == region1); [r_2, c_2] = find(labels == region2); labels(sub2ind(size(labels), r_1, c_1)) = regionNum+1; labels(sub2ind(size(labels), r_2, c_2)) = regionNum+2; regionNum = regionNum + 2; end % 合并区域 for i = 1:regionNum [r, c] = find(labels == i); if (length(r) < minRegionSize || length(r) > maxRegionSize) labels(sub2ind(size(labels), r, c)) = 0; end end % 显示分割结果 figure;imshow(label2rgb(labels));title('分割结果'); ``` 在代码中,我们首先读取图像,并将其转换为灰度图像。然后,我们初始化区域,并设置分割参数。接下来,我们进行区域分割,直到满足停止条件。在每一次分割中,我们计算每个区域的平均灰度值以及每个区域与相邻区域的平均灰度值差,找到差值最大的区域并进行分裂。最后,我们合并区域,并显示分割结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值