// 注:本内容为作者原创,禁止在其他网站复述内容以及用于商业盈利,如需引用,请标明出处: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 = 1:x for j = 1: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[8][2] { {-1,-1}, {-1, 0}, {-1, 1}, { 0,-1}, { 0, 1}, { 1,-1}, { 1, 0}, { 1, 1} }; constexpr size_t H = 2012; constexpr size_t L = 1881; 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[2012 + 1][1881 + 1]; bool reset[H + 1][L + 1]; 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 = 1; i <= x; ++i) for (int j = 1; j <= y; ++j) infile >> _img[i][j]; infile.close(); } void regional::bfs() { queue<pos> Qcurrent; set<pos> Qnew; Qnew.insert(pos(1, 1)); 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 = 0; trans < 8; ++trans) { pos p(seed._x + dir[trans][0], seed._y + dir[trans][1]); if (p._x > 0 && p._x <= H && p._y > 0 && 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 = 1; i <= H; ++i) { for (int j = 1; 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 = 1; i <= H; ++i) for (int j = 1; j <= L; ++j) infile >> _img[i][j]; infile.close(); } void regional::gograph()const { ofstream file; auto left = [&](int cnt) { for (int i = (cnt - 1) * 700 + 1; i <= 700 * cnt; ++i) for (int j = 1; j <= 1000; ++j) file << (int)((0.2 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.5 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.7 + float(_img[i][j] % 10) / 10)*_img[i][j]) << endl; }; auto right = [&](int cnt) { for (int i = (cnt - 1) * 700 + 1; i <= 700 * cnt; ++i) for (int j = L - 1000 + 1; j <= L; ++j) file << (int)((0.2 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.5 + float(_img[i][j] % 10) / 10)*_img[i][j]) << " " << (int)((0.7 + float(_img[i][j] % 10) / 10)*_img[i][j]) << endl; }; file.open("slip1'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(1); file.close(); file.open("slip2'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(1); file.close(); file.open("slip3'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(2); file.close(); file.open("slip4'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(2); file.close(); file.open("slip5'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; left(3); file.close(); file.open("slip6'.ppm"); file << "P3" << endl; file << 1000 << " " << 700 << endl; file << "255" << endl; right(3); file.close(); }
main.cpp
#include "regional.h" using namespace region; #include <iostream> #include <fstream> #define stds std:: int main() { regional reg(25); reg.readfile(); reg.bfs(); reg.out(); //reg.readout(); reg.gograph(); stds cout << "complished" << stds endl; return 0; }
感谢您的阅读,生活愉快~