Cesium 中结合 OpenCV.js 对影像图层进行分割,并将结果转为 GeoJSON 加载到地图

在 Cesium 中结合 OpenCV.js 对影像图层进行分割,并将结果转为 GeoJSON 加载到地图,需要以下步骤:


1. 获取 Cesium 影像数据

首先,需要从 Cesium 的 ImageryLayer 中提取当前视图的影像像素数据(RGB 或 RGBA)。

const viewer = new Cesium.Viewer('cesiumContainer');

// 获取当前激活的影像图层(如 Bing 地图或自定义 WMS)
const imageryLayer = viewer.imageryLayers.get(0);

// 获取当前视图的 Canvas
const canvas = viewer.scene.canvas;
const context = canvas.getContext('2d');

// 创建一个临时 Canvas 用于提取影像数据
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d');

// 将 Cesium 影像绘制到临时 Canvas
tempCtx.drawImage(canvas, 0, 0);
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);

2. 使用 OpenCV.js 进行图像分割

加载 OpenCV.js 并应用图像分割算法(如 阈值分割、边缘检测、语义分割)。

(1) 加载 OpenCV.js

在 HTML 中引入 OpenCV.js:

<script async src="https://docs.opencv.org/4.5.5/opencv.js" onload="onOpenCvReady();"></script>
<script>
  function onOpenCvReady() {
    console.log("OpenCV.js is ready!");
  }
</script>

(2) 图像分割(示例:基于颜色阈值的水体提取)

function segmentWater(imageData) {
  // 将 ImageData 转为 OpenCV Mat
  const src = cv.matFromImageData(imageData);
  const dst = new cv.Mat();

  // 转换到 HSV 颜色空间(更容易提取水体)
  const hsv = new cv.Mat();
  cv.cvtColor(src, hsv, cv.COLOR_RGBA2RGB);
  cv.cvtColor(hsv, hsv, cv.COLOR_RGB2HSV);

  // 定义水体颜色范围(HSV 格式)
  const lower = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [90, 50, 50]); // 浅蓝色下限
  const upper = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [130, 255, 255]); // 浅蓝色上限

  // 应用颜色阈值
  const mask = new cv.Mat();
  cv.inRange(hsv, lower, upper, mask);

  // 查找轮廓
  const contours = new cv.MatVector();
  const hierarchy = new cv.Mat();
  cv.findContours(mask, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);

  // 返回轮廓数据
  return { contours, hierarchy };
}

3. 提取轮廓并转为 GeoJSON

将 OpenCV 检测到的轮廓(contours)转换为 GeoJSON 格式。

(1) 轮廓坐标转换

由于 OpenCV 返回的是 像素坐标,需要将其映射回 经纬度坐标

function contoursToGeoJSON(contours, viewer) {
  const features = [];

  for (let i = 0; i < contours.size(); i++) {
    const contour = contours.get(i);
    const coordinates = [];

    // 遍历轮廓点
    for (let j = 0; j < contour.rows; j++) {
      const point = contour.data32S.slice(j * 2, j * 2 + 2); // [x, y] 像素坐标
      
      // 将像素坐标转为经纬度
      const cartesian = viewer.scene.camera.pickEllipsoid(
        new Cesium.Cartesian2(point[0], point[1]),
        viewer.scene.globe.ellipsoid
      );
      if (cartesian) {
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        const lon = Cesium.Math.toDegrees(cartographic.longitude);
        const lat = Cesium.Math.toDegrees(cartographic.latitude);
        coordinates.push([lon, lat]);
      }
    }

    // 生成 GeoJSON Feature
    if (coordinates.length > 2) { // 至少 3 个点才能形成多边形
      features.push({
        type: "Feature",
        geometry: {
          type: "Polygon",
          coordinates: [coordinates] // GeoJSON 要求闭合环
        }
      });
    }
  }

  return {
    type: "FeatureCollection",
    features
  };
}

4. 加载 GeoJSON 到 Cesium

将生成的 GeoJSON 数据加载到 Cesium 进行可视化:

const { contours, hierarchy } = segmentWater(imageData);
const geoJSON = contoursToGeoJSON(contours, viewer);

// 加载 GeoJSON
Cesium.GeoJsonDataSource.load(geoJSON, {
  stroke: Cesium.Color.RED,
  fill: Cesium.Color.BLUE.withAlpha(0.3),
  strokeWidth: 2
}).then(dataSource => {
  viewer.dataSources.add(dataSource);
  viewer.zoomTo(dataSource);
});

// 释放 OpenCV 内存
src.delete(); hsv.delete(); mask.delete(); contours.delete(); hierarchy.delete();

5. 完整代码示例

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Cesium + OpenCV.js 水库分割</title>
  <script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
  <script async src="https://docs.opencv.org/4.5.5/opencv.js" onload="onOpenCvReady();"></script>
  <style>
    #cesiumContainer { width: 100%; height: 100%; }
  </style>
</head>
<body>
  <div id="cesiumContainer"></div>
  <button id="extractButton">提取水库边界</button>
  <script>
    const viewer = new Cesium.Viewer('cesiumContainer', {
      imageryProvider: new Cesium.BingMapsImageryProvider({
        url: 'https://dev.virtualearth.net',
        key: 'Your_Bing_Maps_Key',
        mapStyle: Cesium.BingMapsStyle.AERIAL
      }),
      baseLayerPicker: false
    });

    let isOpenCvReady = false;
    function onOpenCvReady() {
      isOpenCvReady = true;
      console.log("OpenCV.js loaded!");
    }

    document.getElementById('extractButton').addEventListener('click', () => {
      if (!isOpenCvReady) {
        alert("OpenCV.js 尚未加载完成!");
        return;
      }
      extractReservoirBoundary();
    });

    function extractReservoirBoundary() {
      const canvas = viewer.scene.canvas;
      const tempCanvas = document.createElement('canvas');
      tempCanvas.width = canvas.width;
      tempCanvas.height = canvas.height;
      const tempCtx = tempCanvas.getContext('2d');
      tempCtx.drawImage(canvas, 0, 0);
      const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);

      // OpenCV 处理
      const src = cv.matFromImageData(imageData);
      const hsv = new cv.Mat();
      cv.cvtColor(src, hsv, cv.COLOR_RGBA2RGB);
      cv.cvtColor(hsv, hsv, cv.COLOR_RGB2HSV);

      const lower = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [90, 50, 50]);
      const upper = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [130, 255, 255]);
      const mask = new cv.Mat();
      cv.inRange(hsv, lower, upper, mask);

      const contours = new cv.MatVector();
      const hierarchy = new cv.Mat();
      cv.findContours(mask, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);

      // 转为 GeoJSON
      const geoJSON = {
        type: "FeatureCollection",
        features: []
      };

      for (let i = 0; i < contours.size(); i++) {
        const contour = contours.get(i);
        const coordinates = [];
        for (let j = 0; j < contour.rows; j++) {
          const point = contour.data32S.slice(j * 2, j * 2 + 2);
          const cartesian = viewer.scene.camera.pickEllipsoid(
            new Cesium.Cartesian2(point[0], point[1]),
            viewer.scene.globe.ellipsoid
          );
          if (cartesian) {
            const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
            coordinates.push([
              Cesium.Math.toDegrees(cartographic.longitude),
              Cesium.Math.toDegrees(cartographic.latitude)
            ]);
          }
        }
        if (coordinates.length > 2) {
          geoJSON.features.push({
            type: "Feature",
            geometry: {
              type: "Polygon",
              coordinates: [coordinates]
            }
          });
        }
      }

      // 加载到 Cesium
      Cesium.GeoJsonDataSource.load(geoJSON, {
        stroke: Cesium.Color.RED,
        fill: Cesium.Color.BLUE.withAlpha(0.3),
        strokeWidth: 2
      }).then(dataSource => {
        viewer.dataSources.add(dataSource);
        viewer.zoomTo(dataSource);
      });

      // 释放内存
      src.delete(); hsv.delete(); mask.delete(); contours.delete(); hierarchy.delete();
    }
  </script>
</body>
</html>

关键点总结

  1. OpenCV.js 处理:使用颜色阈值(HSV)或深度学习模型(需额外训练)分割水体。
  2. 坐标转换:将 OpenCV 检测的像素坐标映射回 Cesium 的经纬度坐标。
  3. GeoJSON 生成:确保多边形闭合(首尾点相同),并加载到 Cesium。
  4. 性能优化
    • 限制处理区域(ROI)以提高速度。
    • 使用 WebWorker 避免界面卡顿。

适用于 水库、湖泊、河流等水体边界提取,也可扩展至其他地物分类(如建筑、森林)。

### 实现 CesiumGeoJSON 数据加载与交互 #### 加载 GeoJSON 文件显示图形 为了在 Cesium加载 GeoJSON 文件,首先要确保已安装 Cesium 库及其必要依赖项[^2]。下面是一个简单的例子展示如何加载 GeoJSON 将其作为实体添加到场景中: ```javascript // 假设已有初始化好的 viewer 对象 const geoJsonUrl = 'path/to/your.geojson'; Cesium.GeoJsonDataSource.load(geoJsonUrl).then(function(dataSource) { viewer.dataSources.add(dataSource); // 自动缩放至数据范围 viewer.zoomTo(dataSource); }); ``` 此代码片段会异步获取指定路径下的 GeoJSON 文件,创建一个新的 `GeoJsonDataSource` 来解析该文件的内容。 #### 添加基本交互功能 对于希望增强用户体验的情况来说,可以为这些地理要素增加一些基础的鼠标事件监听器,比如点击、悬停等效果。这里提供了一个关于设置弹窗提示信息的例子: ```javascript viewer.entities.removeAll(); // 清除现有实体以防冲突 let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); handler.setInputAction(function(click) { let pickedObject = viewer.scene.pick(click.position); if (Cesium.defined(pickedObject)) { console.log('Clicked:', pickedObject.id.properties.name._value); // 输出被选中的对象名称 alert(`You clicked on ${pickedObject.id.properties.name._value}`); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ``` 上述脚本实现了当用户单击某个特定位置时触发相应操作的功能;如果检测到了有效的拾取目标,则会在控制台打印其名字属性通过警告框告知用户所点击的是哪个特征点。 另外值得注意的一点是在不同的 zoom level 下采用不同精细度的数据有助于提高性能表现[^1]。这意味着可以根据当前视图级别动态切换要渲染的数据集版本——远处使用低分辨率模型而近处则呈现高细节版本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小赖同学啊

感谢上帝的投喂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值