当前位置: 首页 > news >正文

第08章-几何输出函数

第08章:几何输出函数

8.1 几何输出函数概述

PostGIS 提供了多种几何输出函数,用于将几何对象转换为各种格式的文本或二进制表示。这些函数对于数据交换、可视化和与其他系统集成至关重要。

8.1.1 输出格式分类

┌─────────────────────────────────────────────────────────────┐
│                    几何输出格式分类                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  文本格式                                                    │
│  ├── WKT (Well-Known Text)         - OGC 标准文本格式       │
│  ├── EWKT (Extended WKT)           - PostGIS 扩展文本格式   │
│  ├── GeoJSON                       - JSON 格式              │
│  ├── GML (Geography Markup)        - XML 格式               │
│  ├── KML (Keyhole Markup)          - Google Earth 格式      │
│  └── SVG (Scalable Vector)         - 可缩放矢量图形         │
│                                                             │
│  二进制格式                                                  │
│  ├── WKB (Well-Known Binary)       - OGC 标准二进制格式     │
│  ├── EWKB (Extended WKB)           - PostGIS 扩展二进制格式 │
│  ├── TWKB (Tiny WKB)               - 紧凑二进制格式         │
│  └── MVT (Mapbox Vector Tile)      - 矢量瓦片格式           │
│                                                             │
│  其他格式                                                    │
│  ├── Encoded Polyline              - Google 编码折线        │
│  ├── GeoHash                       - 空间哈希               │
│  └── LatLonText                    - 经纬度文本             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.2 WKT 输出

8.2.1 ST_AsText (WKT)

-- 基本用法
SELECT ST_AsText(ST_MakePoint(116.4, 39.9));
-- POINT(116.4 39.9)SELECT ST_AsText(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- LINESTRING(0 0,1 1)SELECT ST_AsText(ST_MakeEnvelope(0, 0, 1, 1));
-- POLYGON((0 0,0 1,1 1,1 0,0 0))-- 3D 几何
SELECT ST_AsText(ST_MakePoint(116.4, 39.9, 45.5));
-- POINT Z (116.4 39.9 45.5)-- 带 M 值
SELECT ST_AsText(ST_MakePointM(116.4, 39.9, 100));
-- POINT M (116.4 39.9 100)-- 4D 几何
SELECT ST_AsText(ST_GeomFromText('POINT ZM(116.4 39.9 45.5 100)'));
-- POINT ZM (116.4 39.9 45.5 100)-- 控制精度(小数位数)
SELECT ST_AsText(ST_MakePoint(116.40740001, 39.90420001), 4);
-- POINT(116.4074 39.9042)SELECT ST_AsText(ST_MakePoint(116.40740001, 39.90420001), 2);
-- POINT(116.41 39.9)-- 多几何类型
SELECT ST_AsText(ST_Collect(ST_MakePoint(0, 0),ST_MakePoint(1, 1)
));
-- MULTIPOINT((0 0),(1 1))

8.2.2 ST_AsEWKT (Extended WKT)

-- EWKT 包含 SRID 信息
SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326));
-- SRID=4326;POINT(116.4 39.9)SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.4, 39.9, 45.5), 4326));
-- SRID=4326;POINT Z (116.4 39.9 45.5)-- 控制精度
SELECT ST_AsEWKT(ST_SetSRID(ST_MakePoint(116.40740001, 39.90420001), 4326), 4);
-- SRID=4326;POINT(116.4074 39.9042)-- 与 ST_AsText 的区别
SELECT ST_AsText(geom) AS wkt,ST_AsEWKT(geom) AS ewkt
FROM poi LIMIT 1;
-- wkt: POINT(116.4 39.9)
-- ewkt: SRID=4326;POINT(116.4 39.9)

8.3 WKB 输出

8.3.1 ST_AsBinary (WKB)

-- 获取 WKB(十六进制显示)
SELECT ST_AsBinary(ST_MakePoint(116.4, 39.9));
-- 返回 bytea 类型-- 转换为十六进制字符串
SELECT encode(ST_AsBinary(ST_MakePoint(116.4, 39.9)), 'hex');-- 指定字节序
-- 'NDR': 小端序(默认)
-- 'XDR': 大端序
SELECT encode(ST_AsBinary(ST_MakePoint(0, 0), 'NDR'), 'hex');
SELECT encode(ST_AsBinary(ST_MakePoint(0, 0), 'XDR'), 'hex');-- WKB 不包含 SRID
SELECT ST_SRID(ST_GeomFromWKB(ST_AsBinary(ST_SetSRID(ST_MakePoint(0, 0), 4326))));
-- 0(SRID 丢失)

8.3.2 ST_AsEWKB (Extended WKB)

-- EWKB 包含 SRID
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)), 'hex');-- 保留 SRID
SELECT ST_SRID(ST_GeomFromEWKB(ST_AsEWKB(ST_SetSRID(ST_MakePoint(0, 0), 4326))));
-- 4326-- 指定字节序
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(0, 0), 4326), 'XDR'), 'hex');

8.3.3 ST_AsHEXEWKB

-- 直接返回十六进制字符串
SELECT ST_AsHEXEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326));
-- 0101000020E610000066666666662D5D4066666666E6F34340-- 等价于
SELECT encode(ST_AsEWKB(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)), 'hex');

8.3.4 ST_AsTWKB (Tiny WKB)

-- TWKB 是一种紧凑的二进制格式
-- 适合网络传输和存储-- 基本用法
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4, 39.9)), 'hex');-- 控制精度(XY 精度)
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4074, 39.9042), 4), 'hex');  -- 4位小数
SELECT encode(ST_AsTWKB(ST_MakePoint(116.4074, 39.9042), 2), 'hex');  -- 2位小数-- 完整参数
-- ST_AsTWKB(geom, precision_xy, precision_z, precision_m, include_sizes, include_bboxes)
SELECT encode(ST_AsTWKB(ST_SetSRID(ST_MakePoint(116.4074, 39.9042, 45.5), 4326),6,    -- XY 精度(6位小数)2,    -- Z 精度0,    -- M 精度true, -- 包含大小信息true  -- 包含边界框
), 'hex');-- 比较不同格式的大小
SELECT pg_column_size(ST_AsBinary(geom)) AS wkb_size,pg_column_size(ST_AsEWKB(geom)) AS ewkb_size,pg_column_size(ST_AsTWKB(geom, 6)) AS twkb_size
FROM complex_polygons
LIMIT 5;-- TWKB 聚合
SELECT encode(ST_AsTWKB(array_agg(geom),ARRAY[id]::bigint[],  -- ID 数组6
), 'hex')
FROM poi
WHERE category = '餐饮';

8.4 GeoJSON 输出

8.4.1 ST_AsGeoJSON

-- 基本用法
SELECT ST_AsGeoJSON(ST_MakePoint(116.4, 39.9));
-- {"type":"Point","coordinates":[116.4,39.9]}SELECT ST_AsGeoJSON(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- {"type":"LineString","coordinates":[[0,0],[1,1]]}SELECT ST_AsGeoJSON(ST_MakeEnvelope(0, 0, 1, 1));
-- {"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}-- 控制精度
SELECT ST_AsGeoJSON(ST_MakePoint(116.40740001, 39.90420001), 4);
-- {"type":"Point","coordinates":[116.4074,39.9042]}-- 包含 CRS 信息
SELECT ST_AsGeoJSON(ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326), 15, 2);
-- {"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[116.4,39.9]}-- 参数说明
-- ST_AsGeoJSON(geom, maxdecimaldigits, options)
-- options 位掩码:
-- 0 = 无选项
-- 1 = 包含边界框
-- 2 = 使用短 CRS(EPSG:xxxx)
-- 4 = 使用长 CRS(urn:ogc:def:crs:EPSG::xxxx)
-- 8 = GeoJSON 规范严格模式-- 包含边界框
SELECT ST_AsGeoJSON(ST_MakeEnvelope(0, 0, 1, 1), 15, 1);
-- {"type":"Polygon","bbox":[0,0,1,1],"coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}-- 3D 几何
SELECT ST_AsGeoJSON(ST_MakePoint(116.4, 39.9, 45.5));
-- {"type":"Point","coordinates":[116.4,39.9,45.5]}

8.4.2 构建完整的 GeoJSON Feature

-- 创建 GeoJSON Feature
SELECT json_build_object('type', 'Feature','geometry', ST_AsGeoJSON(geom)::json,'properties', json_build_object('id', id,'name', name,'category', category)
)
FROM poi
WHERE id = 1;-- 创建 GeoJSON FeatureCollection
SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type', 'Feature','geometry', ST_AsGeoJSON(geom)::json,'properties', json_build_object('id', id,'name', name,'category', category)))
)
FROM poi
WHERE category = '餐饮';-- 使用 row_to_json 简化
SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type', 'Feature','geometry', ST_AsGeoJSON(geom, 6)::json,'properties', to_jsonb(t) - 'geom'))
)
FROM (SELECT id, name, category, geom FROM poi) t;

8.4.3 使用 jsonb 函数

-- 使用 jsonb 构建(更高效)
SELECT jsonb_build_object('type', 'FeatureCollection','features', jsonb_agg(jsonb_build_object('type', 'Feature','id', id,'geometry', ST_AsGeoJSON(geom)::jsonb,'properties', jsonb_build_object('name', name,'category', category)))
)
FROM poi
WHERE ST_Intersects(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));

8.5 GML 和 KML 输出

8.5.1 ST_AsGML

-- 基本 GML 输出
SELECT ST_AsGML(ST_MakePoint(116.4, 39.9));
-- <gml:Point><gml:coordinates>116.4,39.9</gml:coordinates></gml:Point>-- GML 3 格式
SELECT ST_AsGML(3, ST_MakePoint(116.4, 39.9));
-- <gml:Point><gml:pos>116.4 39.9</gml:pos></gml:Point>-- 控制精度
SELECT ST_AsGML(3, ST_MakePoint(116.40740001, 39.90420001), 4);-- 包含 SRID
SELECT ST_AsGML(3, ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326), 15, 1);
-- 添加 srsName 属性-- 完整参数
-- ST_AsGML(version, geom, maxdecimaldigits, options, nprefix, id)
SELECT ST_AsGML(3,                    -- GML 版本ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326),6,                    -- 精度17,                   -- options: 1(srsName) + 16(namespace)'custom',             -- 命名空间前缀'point1'              -- ID
);-- options 位掩码:
-- 1 = 输出 srsName
-- 2 = 使用长 SRID URN
-- 4 = GML 3.2 格式
-- 16 = 声明命名空间
-- 32 = LineString 使用 srsDimension

8.5.2 ST_AsKML

-- 基本 KML 输出
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9));
-- <Point><coordinates>116.4,39.9</coordinates></Point>-- 控制精度
SELECT ST_AsKML(ST_MakePoint(116.40740001, 39.90420001), 4);
-- <Point><coordinates>116.4074,39.9042</coordinates></Point>-- 3D 几何(KML 使用高程)
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9, 45.5));
-- <Point><coordinates>116.4,39.9,45.5</coordinates></Point>-- 带名称前缀
SELECT ST_AsKML(ST_MakePoint(116.4, 39.9), 15, 'kml');
-- <kml:Point><kml:coordinates>116.4,39.9</kml:coordinates></kml:Point>-- 创建完整的 KML 文档
SELECT '<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2"><Document><name>POI 数据</name>' ||
string_agg('<Placemark><name>' || name || '</name><description>' || COALESCE(category, '') || '</description>' ||ST_AsKML(geom) ||'</Placemark>',''
) ||'</Document>
</kml>'
FROM poi
WHERE category = '景点';

8.6 SVG 输出

8.6.1 ST_AsSVG

-- 基本 SVG 路径
SELECT ST_AsSVG(ST_MakePoint(116.4, 39.9));
-- cx="116.4" cy="-39.9"  (注意 Y 轴翻转)SELECT ST_AsSVG(ST_MakeLine(ST_MakePoint(0, 0), ST_MakePoint(1, 1)));
-- M 0 0 L 1 -1SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1));
-- M 0 0 L 0 -1 1 -1 1 0 Z-- 控制精度和相对/绝对路径
-- ST_AsSVG(geom, rel, maxdecimaldigits)
-- rel = 0: 绝对路径 (M, L)
-- rel = 1: 相对路径 (m, l)SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1), 0, 2);  -- 绝对路径
SELECT ST_AsSVG(ST_MakeEnvelope(0, 0, 1, 1), 1, 2);  -- 相对路径-- 创建 SVG 文档
WITH bounds AS (SELECT ST_XMin(ST_Collect(geom)) AS min_x,ST_YMin(ST_Collect(geom)) AS min_y,ST_XMax(ST_Collect(geom)) AS max_x,ST_YMax(ST_Collect(geom)) AS max_yFROM districts
),
svg_paths AS (SELECT id, name, ST_AsSVG(geom, 0, 2) AS pathFROM districts
)
SELECT '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' || min_x || ' ' || (-max_y) || ' ' || (max_x - min_x) || ' ' || (max_y - min_y) || '">' ||string_agg('<path d="' || path || '" fill="steelblue" stroke="white" stroke-width="0.01"/>', '') ||'</svg>'
FROM bounds, svg_paths
GROUP BY min_x, min_y, max_x, max_y;

8.7 其他输出格式

8.7.1 ST_AsEncodedPolyline

-- Google 编码折线(用于 Google Maps API)
SELECT ST_AsEncodedPolyline(ST_GeomFromText('LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)', 4326)
);
-- _p~iF~ps|U_ulLnnqC_mqNvxq`@-- 控制精度(默认 5)
SELECT ST_AsEncodedPolyline(ST_GeomFromText('LINESTRING(-120.2 38.5,-120.95 40.7)', 4326),6  -- 6 位精度
);-- 解码
SELECT ST_AsText(ST_LineFromEncodedPolyline('_p~iF~ps|U_ulLnnqC_mqNvxq`@'));

8.7.2 ST_GeoHash

-- 生成 GeoHash
SELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042));
-- wx4g0ffe3d (默认完整精度)-- 控制精度
SELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042), 8);
-- wx4g0ffeSELECT ST_GeoHash(ST_MakePoint(116.4074, 39.9042), 5);
-- wx4g0-- 从 GeoHash 创建几何(边界框)
SELECT ST_AsText(ST_GeomFromGeoHash('wx4g0'));
-- POLYGON((116.3671875 39.8583984375, ...))-- 获取 GeoHash 的点(中心点)
SELECT ST_AsText(ST_PointFromGeoHash('wx4g0'));-- GeoHash 网格分析
SELECT ST_GeoHash(geom, 5) AS geohash,COUNT(*) AS poi_count
FROM poi
GROUP BY ST_GeoHash(geom, 5)
ORDER BY poi_count DESC;

8.7.3 ST_AsLatLonText

-- 度分秒格式
SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042));
-- 39°54'15.120"N 116°24'26.640"E-- 自定义格式
SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042), 'D°M''S.SSS"');
-- 39°54'15.120" 116°24'26.640"SELECT ST_AsLatLonText(ST_MakePoint(116.4074, 39.9042), 'D.DDD°');
-- 39.904° 116.407°

8.7.4 ST_AsX3D

-- X3D 格式(3D 可视化)
SELECT ST_AsX3D(ST_MakePoint(116.4, 39.9, 45.5));
-- 116.4 39.9 45.5SELECT ST_AsX3D(ST_GeomFromText('POLYGON Z((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))'));-- 控制精度
SELECT ST_AsX3D(ST_MakePoint(116.40740001, 39.90420001, 45.5), 4);

8.8 MVT 输出

8.8.1 ST_AsMVTGeom

-- 准备 MVT 几何(坐标转换到瓦片坐标系)
SELECT ST_AsMVTGeom(geom,ST_TileEnvelope(14, 13527, 6214),  -- z/x/y 瓦片边界4096,  -- extent(瓦片像素尺寸)0,     -- buffertrue   -- clip to tile
) AS mvt_geom
FROM poi
WHERE geom && ST_TileEnvelope(14, 13527, 6214);

8.8.2 ST_AsMVT

-- 生成 MVT 瓦片
SELECT ST_AsMVT(tile, 'poi') AS mvt
FROM (SELECT id,name,category,ST_AsMVTGeom(geom,ST_TileEnvelope(14, 13527, 6214),4096,64,true) AS geomFROM poiWHERE geom && ST_TileEnvelope(14, 13527, 6214)
) AS tile;-- 创建 MVT 瓦片函数
CREATE OR REPLACE FUNCTION get_mvt_tile(z INT, x INT, y INT)
RETURNS bytea AS $$
DECLAREtile_bounds geometry;mvt bytea;
BEGINtile_bounds := ST_TileEnvelope(z, x, y);SELECT ST_AsMVT(tile, 'poi', 4096, 'geom') INTO mvtFROM (SELECT id,name,category,ST_AsMVTGeom(geom,tile_bounds,4096,64,true) AS geomFROM poiWHERE geom && tile_bounds) AS tile;RETURN mvt;
END;
$$ LANGUAGE plpgsql STABLE PARALLEL SAFE;-- 使用
SELECT get_mvt_tile(14, 13527, 6214);-- 多图层 MVT
SELECT ST_AsMVT(poi_layer, 'poi') || ST_AsMVT(road_layer, 'roads') AS mvt
FROM (SELECT id, name, ST_AsMVTGeom(geom, bounds, 4096, 64, true) AS geomFROM poi, (SELECT ST_TileEnvelope(14, 13527, 6214) AS bounds) bWHERE geom && bounds
) poi_layer,
(SELECT id, name, ST_AsMVTGeom(geom, bounds, 4096, 64, true) AS geomFROM roads, (SELECT ST_TileEnvelope(14, 13527, 6214) AS bounds) bWHERE geom && bounds
) road_layer;

8.9 格式比较与选择

8.9.1 格式特性对比

格式 人类可读 大小 SRID 3D支持 用途
WKT 调试、日志
EWKT 数据交换
WKB 程序间传输
EWKB PostGIS 内部
TWKB 网络传输
GeoJSON 部分 Web API
GML 很大 OGC 服务
KML Google Earth
MVT 矢量瓦片

8.9.2 选择建议

-- Web API(推荐 GeoJSON)
SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type', 'Feature','geometry', ST_AsGeoJSON(ST_Simplify(geom, 0.0001), 6)::json,'properties', json_build_object('id', id, 'name', name)))
)
FROM districts;-- 数据备份(推荐 EWKT 或 EWKB)
COPY (SELECT id, name, ST_AsEWKT(geom) AS geomFROM spatial_data
) TO '/tmp/backup.csv' WITH CSV HEADER;-- 移动应用(推荐 TWKB)
SELECT id, name, encode(ST_AsTWKB(geom, 5), 'base64') AS geom
FROM poi
WHERE category = '餐饮';-- 地图瓦片(推荐 MVT)
SELECT get_mvt_tile(z, x, y);-- Google Earth(推荐 KML)
SELECT ST_AsKML(geom) FROM poi;

8.10 本章小结

本章详细介绍了 PostGIS 的几何输出函数:

  1. WKT 输出:ST_AsText、ST_AsEWKT
  2. WKB 输出:ST_AsBinary、ST_AsEWKB、ST_AsTWKB
  3. GeoJSON 输出:ST_AsGeoJSON 及 FeatureCollection 构建
  4. GML/KML 输出:ST_AsGML、ST_AsKML
  5. SVG 输出:ST_AsSVG
  6. 其他格式:编码折线、GeoHash、X3D
  7. MVT 输出:矢量瓦片生成

8.11 下一步

在下一章中,我们将学习空间关系函数,包括:

  • 拓扑关系判断
  • 距离关系
  • 方向关系
  • DE-9IM 模型

相关资源

  • PostGIS 输出函数
  • GeoJSON 规范
  • KML 参考
  • MVT 规范
http://www.zskr.cn/news/174347.html

相关文章:

  • 3步掌握ClickHouse地理空间分析:告别传统GIS系统臃肿配置实战指南
  • Serial-Studio数据可视化方案抉择:从成本控制到技术实施的最佳实践
  • BindCraft:让蛋白质分子设计变得简单高效的AI工具
  • AI编程的残酷真相:为什么说Spec Coding是2026年最大的趋势?
  • 第01章-NPOI概述与入门
  • 测试用例:不能再细分的小案例,一组集合
  • 2025年度个人出书机构排名:个人出书流程全揭秘!哪些靠谱机构值得选? - myqiye
  • 贪心算法专题(六):步步为营的极速狂飙——「跳跃游戏 II」
  • 使用Fiddler捕获并分析特定网站的请求。
  • 第06章-Excel公式与函数
  • 2025年口碑好有创新技术的护肤连锁品牌排名:5大靠谱护肤连锁品牌选择推荐 - 工业品牌热点
  • 2025靠谱的种子展会企业TOP5权威推荐:甄选助力种业高效对接 - mypinpai
  • 【高斯泼溅】3DGS城市模型从“硬盘杀手”到“轻盈舞者”?看我们如何实现14倍压缩
  • 如何为Windows 11虚拟机打造铜墙铁壁?VMware Workstation 18技术预览版深度评测
  • 收藏!彻底搞懂Transformer:不用数学公式,只用生活案例讲透AI大模型原理
  • 第07章-Excel数据验证与保护
  • 蓝绿部署下的自动化测试验证:构建高可靠软件交付的核心引擎
  • 阅读3.0书源优化完全指南:从资源匮乏到高效管理
  • MeterSphere变量优先级:3层架构解密与实战避坑指南
  • 2025年标识标牌生产厂家Top5 - 2025年品牌推荐榜
  • WriteGPT 人工智能写作框架终极指南:从零开始构建智能创作系统
  • 哪个(副)主任护师考试培训机构靠谱?十大医考机构最新测评分享 - 资讯焦点
  • 深度测评:主管护师考试网课怎么选? - 资讯焦点
  • 从物理学转向数据科学:全面指南
  • 2025年评价高的液压冲击台TOP实力厂家推荐榜 - 品牌宣传支持者
  • Qwen1.5本地部署:构建私有智能体的技术革命
  • Red Hat Enterprise Linux 7.0 系统镜像完整获取与安装实战
  • 多智能体系统8大最佳实践:从单智能体到智能网络的进阶之路,赶紧收藏!
  • 深度解密:12-Factor Agents与BAML集成:实现结构化输出的性能飞跃
  • 恒源云GPU云端训练YOLOv11全流程教程:低成本实现高精度目标检测