// 注:本内容为作者原创,禁止在其他网站复述内容以及用于商业盈利,如需引用,请标明出处:https://www.cnblogs.com/lv-anchoret/

今天我们来介绍用C++算法如何来实现图像分割算法中的区域生长算法

区域生长的简介

我们解决的是对一整张图像所有内容进行区域生长分类,当然,如果是对图像中的某一类型进行区域生长可能更容易一些

个人理解

区域生长算法需要确定一个阈值,这个值代表同一类图像信息灰度值的差值,比如,我要一个人脸图(假设眼睛是蓝色的),头发是黑色的但是不同光线反射原因或者位置不同,图像中显示的灰度颜色值有5、10、3等,虽然灰度值不一样,但是他们代表的都是黑色,都是头发,区域生长,就是把这些相似灰度值的一类事物统一标注显示,这也就达到了分类识别的目的,关于阈值,比如上述的头发,我们需要将所有是头发的像素点都标注出来,那么我们的阈值就应该设置为10,如果设置为3,可能灰度值为3和5的点会统一识别,但是10就被排除在外了。

算法核心就是一个bfs,设立一个种子点,向四周扩张,如果相邻的点的灰度值相对于种子点在阈值范围之内,那么,我们把它识别并包含统一进来,扩张完毕之后,所有相似的一类图像将被统一标注。

关于标注我们还需要说一下,一开始,想起了四色定理,即用四种颜色就可以吧整个地图分类标注且相邻类别颜色不同,后来想了想还不如把同一类型区域中的所有点都设置为种子点灰度像素值。

之后想起来我们光线追踪一直用的ppm彩色文件格式,索性将灰度值转成rgb彩色图看着更爽

区域生长算法流程

1. 找种子点

2. 扩张原则

3. 终止条件

数据介绍

我们的数据是一张灰度图 : 见 纹理相册夹中的第二张图

我们处理输出的是一张彩色图像,图像格式是我们光线追踪的文件格式 .ppm,用我们光线追踪的图片解析器(ppmviewer)就能打开(没有的也没关系,搜索一下,下载不超过十几秒,超轻量级ppm解读器)

我们都知道,C/C++ 中读取图像麻烦,特别是这种.jpg复杂格式数据,所以,我们用matlab先把图像读出来,输出到一个TXT中,存储为二维矩阵形式,然后用C++代码读取TXT文件内容,存储到一个二维数据序列中。(都有代码在后面)

我们侧重实现算法本身,所以关于图像的读取和写入我们不做过多描述

算法介绍

算法自命名:首次左上区域生长算法

时间复杂度:O(图像大小*种子数量*log(种子数量))

一、区域生长的三大要素确立:

(1)生长合并规则:

用户自定义阈值,根据种子点和当前点的差值,如果在阈值之内,那么合并,将当前点的灰度值设为种子灰度值

(2)种子选取:

将图像左上角第一个点作为第一个种子,在扩张的过程中第一个不符合生长合并规则的位置,作为下一次生长的种子,即首次选定,后期自适应确定。

(3)算法结束:

种子为空

二、优缺点

该算法的优点:  针对大型全局生长而衍生

该算法种子不会将同一个位置作为种子多次重复生长(时间空间复杂度优化)

某个种子在开始生长时,如果已经被包含于另一个种子的生长区域中,那么该种子将不会进行生长(时间复杂度优化)

该算法的缺点:  首次选定法不能用合适的灰度代表整个区域,只能是坐标小的点的灰度值

生长出来的区域可能不是很完美,因为该区域是由该区域坐标最小的点生长而成的。

三、灰度值转rgb算法设计

因为要将单一的灰度值映射到r、g、b,使其代表的颜色具有独特性

这个可以自己设计,我的设计如下:

四、构架设计

含有一个类 —— regional

数据成员

_img:用于存储图像灰度矩阵

reset:用于记录某个位置的灰度是否被重置

_delt:阈值

成员函数

readfile:读图像灰度矩阵文件

bfs:进行区域生长

out:输出处理后的图像灰度矩阵

readout:读取处理后的图像灰度矩阵文件

gograph:将灰度图像转为rgb图像,由于ppmview显示空间有限,所以将此图划分为6块(将原图像分为3行2列的6块),分别输出6个图像

流程图如下:

五、数据结构设计:(C++描述)

用bfs算法进行的话,必然需要队列,但是种子们不能用队列去存,某个种子进行生长的时候可以用队列记录每一个生长状态

此法采用8领域进行生长

用队列存储当前种子生长过程中的状态点,进行bfs扩展,确定该种子生长所形成的区域

用set容器存储各个种子,保证了种子点唯一性,即优点2,同时,set容器还会根据位置自动排序,所以导致了缺点2,其次,set容器的存取操作的时间复杂度均为O(n log n)

Bfs状态采用只包含x、y坐标的一个结构体

六、效果与讨论

采用C++描述的首次左上区域生长算法针对2012*1881的灰度卫星图像矩阵数据处理时间为:78.9s

阈值为20

              图1

首先蓝色圈所代表的部分,如果是水域的深浅,那么这一块还是被划分的很清楚的,大致分了5部分

再看下阈值为25的图

            图2

如我们所预期的那样,图1中蓝色圈的水深划分等级更少了

其次,我们看图1的红色圈代表的水体,内部中间划分出来的区域更小了,或者说水体的边缘区域扩张了。

再如,黑色圈代表的水域,中间的黑色圈有一块东西,它的区域更小了,不利于捕捉细微的水内状况

如果图1的黑色内的小黑圈部分看不太清楚,那么可以看一下下面这个,都是一样的

XnView打开效果

    图3

如果把黄色部分看做是竖着的地质锤,那么图2显然少了锤头~

还有水边一片房子聚集地,也被基本划分为一种色调

          图4

而针对下图以及图4以及原图上方一片森林山脉,将各种处理方法进行叠加,效果可能会更好,方案如下:

有很多星星点点的噪声,可以选择先去噪声,但是,效果也不是很好

如果要将其纳入到统一的大片区域中,还是选择先做一个平滑处理,将其尖锐的边缘过渡更加平滑些,再进行区域生长,加以阈值调整,星点可能会减少,可能还存在一些,但是不会那么显眼,和周围环境的色差不会那么大了

              图5

七、代码

matlab 代码

matlab:

function writetxt I = imread('poyanghu.jpg'); fid = fopen('image.txt','w');
[x,y] = size(I);
fprintf(fid,'%d %d\n',x,y); for i = :x
for j = :y
fprintf(fid,'%d ',I(i,j));
end
fprintf(fid,'\n');
end fclose(fid);

C++:

regional.h

//regional.h

#pragma once

namespace region
{
constexpr int dir[][]
{
{-,-},
{-, },
{-, },
{ ,-},
{ , },
{ ,-},
{ , },
{ , }
}; constexpr size_t H = ;
constexpr size_t L = ; class regional
{
public:
struct pos
{
int _x, _y;
pos(const int a, const int b) :_x(a), _y(b) { }
bool operator<(const pos& p)const
{
if (_x == p._x)
return _y < p._y;
return _x < p._x;
}
}; public:
regional(const size_t delt); void readfile(); void bfs(); void out(); void readout(); void gograph()const; private: short _img[ + ][ + ]; bool reset[H + ][L + ]; size_t _delt; }; }

regional.cpp

#include "regional.h"
#include <iostream>
#include <fstream>
#include <queue>
#include <set>
using namespace std;
using namespace region; regional::regional(const size_t delt)
:_delt(delt)
{
memset(reset, false, sizeof reset);
} void regional::readfile()
{
ifstream infile;
infile.open("image.txt");
if (!infile.is_open())
cerr << "open failed" << endl;
int x, y;
infile >> x >> y;
for (int i = ; i <= x; ++i)
for (int j = ; j <= y; ++j)
infile >> _img[i][j];
infile.close();
} void regional::bfs()
{
queue<pos> Qcurrent;
set<pos> Qnew;
Qnew.insert(pos(, ));
while (Qnew.size())
{
Qcurrent.push(*Qnew.begin());
Qnew.erase(Qnew.begin());
if (reset[Qcurrent.front()._x][Qcurrent.front()._y])//该种子点已经访问过
{
Qcurrent.pop();
continue;
}
while (Qcurrent.size())
{
pos seed = Qcurrent.front();
reset[seed._x][seed._y] = true;
Qcurrent.pop();
for (int trans = ; trans < ; ++trans)
{
pos p(seed._x + dir[trans][], seed._y + dir[trans][]);
if (p._x > && p._x <= H && p._y > && p._y <= L && !reset[p._x][p._y])
if (abs(_img[p._x][p._y] - _img[seed._x][seed._y]) < _delt)
{
_img[p._x][p._y] = _img[seed._x][seed._y];
reset[p._x][p._y] = true;
Qcurrent.push(p);
}
else
Qnew.insert(p);
} }
}
} void regional::out()
{
ofstream outfile;
outfile.open("outall.txt");
if (!outfile.is_open())
cerr << "open failed" << endl; for (int i = ; i <= H; ++i)
{
for (int j = ; j <= L; ++j)
outfile << _img[i][j] << " ";
outfile << endl;
} outfile.close();
} void regional::readout()
{
ifstream infile("outall.txt");
if (!infile.is_open())
{
cerr << "error open" << endl;
} for (int i = ; i <= H; ++i)
for (int j = ; j <= L; ++j)
infile >> _img[i][j]; infile.close();
} void regional::gograph()const
{
ofstream file; auto left = [&](int cnt)
{
for (int i = (cnt - ) * + ; i <= * cnt; ++i)
for (int j = ; j <= ; ++j)
file << (int)((0.2 + float(_img[i][j] % ) / )*_img[i][j])
<< " " << (int)((0.5 + float(_img[i][j] % ) / )*_img[i][j])
<< " " << (int)((0.7 + float(_img[i][j] % ) / )*_img[i][j]) << endl;
}; auto right = [&](int cnt)
{
for (int i = (cnt - ) * + ; i <= * cnt; ++i)
for (int j = L - + ; j <= L; ++j)
file << (int)((0.2 + float(_img[i][j] % ) / )*_img[i][j])
<< " " << (int)((0.5 + float(_img[i][j] % ) / )*_img[i][j])
<< " " << (int)((0.7 + float(_img[i][j] % ) / )*_img[i][j]) << endl;
}; file.open("slip1'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
left();
file.close(); file.open("slip2'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
right();
file.close(); file.open("slip3'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
left();
file.close(); file.open("slip4'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
right();
file.close(); file.open("slip5'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
left();
file.close(); file.open("slip6'.ppm");
file << "P3" << endl;
file << << " " << << endl;
file << "" << endl;
right();
file.close(); }

main.cpp

#include "regional.h"
using namespace region;
#include <iostream>
#include <fstream>
#define stds std:: int main()
{ regional reg();
reg.readfile();
reg.bfs();
reg.out();
//reg.readout();
reg.gograph();
stds cout << "complished" << stds endl; return ;
}

感谢您的阅读,生活愉快~

区域生长算法 全局分类 C++ & matlab的更多相关文章

  1. 区域生长算法(附MATLAB代码实现)

    一.理论概念 区域生长是按照事先定义的生长准则将一个像素或者子区域逐步聚合成一个完整独立的连通区域过程.对于图像感兴趣目标区域R,z为区域R上事先发现的种子点,按照规定的生长准则逐步将与种子点z一定邻 ...

  2. 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...

  3. 算法杂货铺——分类算法之朴素贝叶斯分类(Naive Bayesian classification)

    算法杂货铺——分类算法之朴素贝叶斯分类(Naive Bayesian classification) 0.写在前面的话 我个人一直很喜欢算法一类的东西,在我看来算法是人类智慧的精华,其中蕴含着无与伦比 ...

  4. 区域生长算法的一种C++实现

    区域生长算法是一种图像分割方法,能够将图像中具有相同特征的连通区域分割出来,同时保证较好的边缘信息. 区域生长算法的优点是简单,容易实现:但空间和时间复杂度较高,对分割图像要求较高,否则容易形成孔洞和 ...

  5. ML之监督学习算法之分类算法一 ———— k-近邻算法(最邻近算法)

    一.概述 最近邻规则分类(K-Nearest Neighbor)KNN算法 由Cover 和Hart在1968年提出了最初的邻近算法, 这是一个分类(classification)算法 输入基于实例的 ...

  6. k-近邻算法 标签分类

    k-近邻算法根据特征比较,然后提取样本集中特征最相似数据(最邻近)的分类标签.那么,如何进行比较呢? 怎么判断红色圆点标记的电影所属的类别呢? 如下图所示. 答:距离度量.这个电影分类的例子有2个特征 ...

  7. 分类算法简介 分类: B10_计算机基础 2015-03-09 11:08 257人阅读 评论(0) 收藏

    一.决策树 决策树是用于分类和预测的主要技术之一,决策树学习是以实例为基础的归纳学习算法,它着眼于从一组无次序.无规则的实例中 推理出以决策树表示的分类规则.构造决策树的目的是找出属性和类别间的关系, ...

  8. 02-19 k近邻算法(鸢尾花分类)

    [TOC] 更新.更全的<机器学习>的更新网站,更有python.go.数据结构与算法.爬虫.人工智能教学等着你:https://www.cnblogs.com/nickchen121/ ...

  9. 【转载】 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    原文地址: https://www.cnblogs.com/steven-yang/p/5686473.html ------------------------------------------- ...

随机推荐

  1. 用Quartz 2D画小黄人

    第一步: 先创建一个OneView类,并在storyboard里边拖拽一个UIview,将这个UIview的类改成OneView.如图所示: 第二步: 在新创建的Oneview里,补齐下列代码: // ...

  2. java 多线程和并行程序设计

    多线程使得程序中的多个任务可以同时执行 在一个程序中允许同时运行多个任务.在许多程序设计语言中,多线程都是通过调用依赖系统的过程或函数来实现的 为什么需要多线程?多个线程如何在单处理器系统中同时运行? ...

  3. Ubuntu16.04+CUDA8.0+cudnn6

    按之前的方法给TITAN X安装cuda8.0会发生循环登录的问题,因此换了一种安装方法 参考:https://www.jianshu.com/p/002ece426793,http://blog.c ...

  4. Linux内存管理2---段机制

    1.前言 本文所述关于内存管理的系列文章主要是对陈莉君老师所讲述的内存管理知识讲座的整理. 本讲座主要分三个主题展开对内存管理进行讲解:内存管理的硬件基础.虚拟地址空间的管理.物理地址空间的管理. 本 ...

  5. 配置samba文件服务器

    1.打开"终端窗口",输入"sudo apt-get update"-->回车-->"输入当前登录用户的管理员密码"--> ...

  6. 基于TLS的EAP 认证方法

    TLS: transport level security , 安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性.该协议由两层组成: TLS 记录协议(TLS Record)和 TLS ...

  7. centos6.5环境下zookeeper-3.4.6集群环境部署及单机部署详解

    centos6.5环境下Zookeeper-3.4.6集群环境部署 [系统]Centos 6.5 集群部署 [软件]准备好jdk环境,此次我们的环境是open_jdk1.8.0_101 zookeep ...

  8. Android数据存储:File

    Android数据存储之File Files:它通过FileInputStream和FileOuputStream对文件进行操作.但是在Android中,文件是一个应用程序私有的,一个应用程序无法读写 ...

  9. TomCat安装配置教程

    一.JDK的安装与配置 1.从官网下载jdk,注意是jdk不是jre.最好从官网下载,也可以直接度娘. 2.下载完毕后,安装jdk,​直接按照安装向导的提示安装即可,安装时可以自己选择安装路径,我的安 ...

  10. bert 词典扩充方案