一、为什么需要骨架提取
简单来说就是用于细化栅格,便于栅格数据转换为矢量数据
栅格格式向矢量格式转换是提取相同编号的栅格集合表示的边界,栅格点转换成矢量点,很简单,在坐标系确定的情况下通过解析式可以直接转换。而线与面在转换成矢量的时候,本质上都是在提取边界或中轴线,因此在栅格中提取中轴线就与栅格的细化的关系密不可分,这是因为线状栅格数据一般具有粗度且线条本身往往呈现粗细。栅格数据需要细化,以提取中轴线。这是因为:
①中轴线是栅格数据曲线的标准化存储形式
②实现细化是将栅格曲线矢量化的前提
③在有些算法中可以提高计算精度
二、距离变换法提取骨架
距离变换图
距离变换图也是一种栅格图像,其中,每个像元值存储了它到栅格图上相邻物体的最近距离。这个距离的量度:可以是曼哈顿距离,棋盘距离,或者欧式距离。这三个距离关系在GIS中很常用。算法实现如下:
/**
*
* @param disType 距离类型
* @param s1 像元1
* @param s2 像元2
* @return
*/
private double calculateDis(DisType disType,Pixel s1,Pixel s2) {
double dis = 0;
switch (disType) {
case Euclidean:
dis = Math.sqrt(Math.pow(s1.getRow()-s2.getRow(), 2)+Math.pow(s1.getColumn()-s2.getColumn(), 2))*size;
break;
case CityBlock:
dis = Math.abs(s1.getRow()-s2.getRow())+Math.abs(s1.getColumn()-s2.getColumn())*size;
break;
case ChessBoard:
dis = Math.max(Math.abs(s1.getRow()-s2.getRow()), Math.abs(s1.getColumn()-s2.getColumn()))*size;
break;
default:
dis = Math.sqrt(Math.pow(s1.getRow()-s2.getRow(), 2)+Math.pow(s1.getColumn()-s2.getColumn(), 2))*size;
break;
}
return dis;
}
基于距离变换法提取骨架算法思想:
对内部点集i到非内部点集e(孤立点与边界点)求最小距离,实际上就是求目标点到最近背景点的距离(背景点-值为0的像元 目标点-值为1的像元),求出距离后 对距离进行分类即可得骨架图
基于距离变换法提取骨架算法步骤:
①将栅格图像进行初始二值化(背景点设为0,目标点设为1)
②将栅格图像进行分类,把栅格分为内部点,边界点,孤立点。
③求每一个内部点到非内部点的距离,距离值赋给栅格值
④对栅格图像进行二值化(距离大于1的栅格值设为1,小于等于1的设为0)
③重复②③④,终止条件为“若下一次栅格图像二值化结果全部为0”
如何分类:
在步骤②中,如何把栅格分为内部点,边界点,孤立点?
以中心像素的四邻域为例,
1、如果中心像素为目标像素(值为1)且四邻域都为目标像素(值为1),则该点为内部点。
2、如果该中心像素为目标像素,四邻域为背景像素(值为0),则该中心点为孤立点。
3、其他情况则为边界点
分类代码实现
//判断是边界点,内部点,孤立点
//前提:栅格已经二值化
public void setNeighbourhood() {
internalPoints = new ArrayList<>();
borderPoints = new ArrayList<>();
for(int i=0;i|
测试结果:
B代表边界点,I代表内部点,孤立点未进行渲染
基于距离变换法提取骨架算法实现
我使用了3*3模板的快速距离变换。
按照从上到下,从左到右的顺序,遍历3x3的栅格图像
但是有个问题是:如果在遍历过程中,碰到了栅格的边界怎么办,所以我写了对应的解决办法,即先确定快速距离变换遍历的范围,在开始遍历快速距离变换。
代码如下:
/**
* 骨架图算法(距离变换法搜索中轴线)
* 对内部点集i到边界点集e求最小距离
* 实际上就是求目标点到最近背景点的距离
* 背景点-值为0的像元 目标点-值为1的像元
* 求出距离后 对距离进行分类即可得骨架图
*/
public void getMinDis(DisType disType) {
//快速距离模板计算
//从左至右,从上到下,顺时针寻找周围是否有边界点
//如果有,则加入计算
//如果没有,则扩大搜寻范围,最大范围到数组越界
//最后得出最小距离
//当前圈层数
if(borderPoints==null && internalPoints==null) {
setNeighbourhood();
}
for(Pixel i:internalPoints) {
List disList = new ArrayList<>();
//搜索范围
int cicleNum = 1;
//上下左右搜寻边界
int up,down,left,right;
int upLimit,downLimt,leftLimit,rightLimit;
upLimit = 1;
downLimt = ROW;
leftLimit = 1;
rightLimit = COLUMN;
//确定遍历范围,防止边界溢出
for(int curCir=0;curCirdownLimt) {
down = downLimt;
}
} catch (Exception e) {
// TODO: handle exception
down = i.getRow();
}
try {
left = i.getColumn()-cicleNum;
if(leftrightLimit) {
right=rightLimit;
}
} catch (Exception e) {
// TODO: handle exception
right = i.getColumn();
}
//记录栅格周边是否有非内部点,没有的话则圈数+1
boolean flag = false;
//从最左最上开始遍历,遍历顺序从左至右,从上到下
for(int row=up;row
测试结果:
原始数据:
距离变换细化一次:
距离变换细化2次
。。。
n次