yooooger
This commit is contained in:
parent
aa702653d7
commit
42f6f1fa02
@ -1,20 +1,19 @@
|
||||
from sanic import Sanic, json, Blueprint,response
|
||||
from sanic.exceptions import Unauthorized, NotFound
|
||||
from sanic.exceptions import Unauthorized
|
||||
from sanic.response import json as json_response
|
||||
from sanic_cors import CORS
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
import logging
|
||||
import uuid
|
||||
import os
|
||||
import os,traceback
|
||||
import asyncio
|
||||
from ai_image import process_images # 你实现的图片处理函数
|
||||
from queue import Queue
|
||||
from map_find import map_process_images
|
||||
from yolo_train import auto_train
|
||||
from cv_video_counter import start_video_session,switch_model_session,stop_video_session,stream_sessions
|
||||
import torch
|
||||
from yolo_photo import map_process_images_with_progress # 引入你的处理函数
|
||||
|
||||
from tiles import TilesetProcessor
|
||||
# 日志配置
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -76,6 +75,80 @@ app.config.update({
|
||||
map_tile_blueprint = Blueprint('map', url_prefix='/map/')
|
||||
app.blueprint(map_tile_blueprint)
|
||||
|
||||
@map_tile_blueprint.post("/compare_tilesets")
|
||||
async def compare_tilesets(request):
|
||||
'''
|
||||
接口:/map/compare_tilesets
|
||||
输入 JSON:
|
||||
{
|
||||
"tileset1": "path/to/tileset1/tileset.json",
|
||||
"tileset2": "path/to/tileset2/tileset.json",
|
||||
"bounds": [500000, 3000000, 500100, 3000100],
|
||||
"resolution": 1.0,
|
||||
"output": "results"
|
||||
}
|
||||
输出 JSON:
|
||||
{
|
||||
"success": true,
|
||||
"message": "分析完成",
|
||||
"data": {
|
||||
"csv_path": "results/height_differences.csv",
|
||||
"heatmap_path": "results/height_difference_heatmap.png",
|
||||
"summary": {
|
||||
"mean": 0.28,
|
||||
"max": 1.10,
|
||||
"min": -0.85,
|
||||
"std": 0.23
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
try:
|
||||
body = request.json
|
||||
|
||||
# 参数提取与验证
|
||||
tileset1 = body.get("tileset1")
|
||||
tileset2 = body.get("tileset2")
|
||||
bounds = body.get("bounds")
|
||||
resolution = body.get("resolution", 1.0)
|
||||
output = body.get("output", "results")
|
||||
|
||||
if not all([tileset1, tileset2, bounds]):
|
||||
return response.json({"success": False, "message": "参数不完整"}, status=400)
|
||||
|
||||
processor = TilesetProcessor(tileset1, tileset2, resolution)
|
||||
|
||||
if not processor.set_analysis_area(bounds):
|
||||
return response.json({"success": False, "message": "设置分析区域失败"}, status=400)
|
||||
|
||||
if not processor.sample_heights():
|
||||
return response.json({"success": False, "message": "高度采样失败"}, status=500)
|
||||
|
||||
processor.export_results(output)
|
||||
|
||||
# 汇总统计结果
|
||||
valid_differences = processor.height_difference_grid[~np.isnan(processor.height_difference_grid)]
|
||||
summary = {
|
||||
"mean": float(np.mean(valid_differences)),
|
||||
"max": float(np.max(valid_differences)),
|
||||
"min": float(np.min(valid_differences)),
|
||||
"std": float(np.std(valid_differences))
|
||||
}
|
||||
|
||||
return response.json({
|
||||
"success": True,
|
||||
"message": "分析完成",
|
||||
"data": {
|
||||
"csv_path": os.path.join(output, "height_differences.csv"),
|
||||
"heatmap_path": os.path.join(output, "height_difference_heatmap.png"),
|
||||
"summary": summary
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return response.json({"success": False, "message": str(e)}, status=500)
|
||||
|
||||
#语义识别
|
||||
@map_tile_blueprint.post("/uav")
|
||||
async def process_handler(request):
|
||||
@ -293,160 +366,5 @@ async def yolo_train_api(request):
|
||||
"message": f"Internal server error: {str(e)}"
|
||||
}, status=500)
|
||||
|
||||
###########################################################################################视频流相关的API#######################################################################################################
|
||||
|
||||
#创建视频流的蓝图
|
||||
stream_tile_blueprint = Blueprint('stream', url_prefix='/stream_test/')
|
||||
app.blueprint(stream_tile_blueprint)
|
||||
|
||||
#
|
||||
# 任务管理器
|
||||
class StreamTaskManager:
|
||||
def __init__(self):
|
||||
self.active_tasks = {}
|
||||
self.task_status = {}
|
||||
self.task_timestamps = {}
|
||||
self.task_queue = Queue(maxsize=10)
|
||||
|
||||
def add_task(self, task_id: str, task_info: dict) -> None:
|
||||
if self.task_queue.full():
|
||||
oldest_task_id = self.task_queue.get()
|
||||
self.remove_task(oldest_task_id)
|
||||
stop_video_session(self.active_tasks[oldest_task_id]["session_id"])
|
||||
self.active_tasks[task_id] = task_info
|
||||
self.task_status[task_id] = "running"
|
||||
self.task_timestamps[task_id] = datetime.now()
|
||||
self.task_queue.put(task_id)
|
||||
logger.info(f"Task {task_id} started")
|
||||
|
||||
def remove_task(self, task_id: str) -> None:
|
||||
if task_id in self.active_tasks:
|
||||
del self.active_tasks[task_id]
|
||||
del self.task_status[task_id]
|
||||
del self.task_timestamps[task_id]
|
||||
logger.info(f"Task {task_id} removed")
|
||||
|
||||
def get_task_info(self, task_id: str) -> dict:
|
||||
if task_id not in self.active_tasks:
|
||||
raise NotFound("Task not found")
|
||||
return {
|
||||
"task_info": self.active_tasks[task_id],
|
||||
"status": self.task_status[task_id],
|
||||
"start_time": self.task_timestamps[task_id].isoformat()
|
||||
}
|
||||
|
||||
task_manager = StreamTaskManager()
|
||||
|
||||
# ---------- API Endpoints ----------
|
||||
|
||||
@stream_tile_blueprint.post("/start")
|
||||
async def api_start(request):
|
||||
"""
|
||||
启动视频流会话
|
||||
输入 JSON:
|
||||
{
|
||||
"video_path": str,
|
||||
"output_url": str,
|
||||
"model_path": str,
|
||||
"cls": List[int],
|
||||
"confidence": float,
|
||||
"cls2": Optional[List[int]]
|
||||
"push": bool
|
||||
}
|
||||
输出 JSON:
|
||||
{
|
||||
"session_id": str,
|
||||
"task_id": str,
|
||||
"message": "started"
|
||||
}
|
||||
"""
|
||||
data = request.json
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
# 启动视频处理会话,并传入 task_id
|
||||
session_id = start_video_session(
|
||||
video_path = data.get("video_path"),
|
||||
output_url = data.get("output_url"),
|
||||
model_path = data.get("model_path"),
|
||||
cls = data.get("cls"),
|
||||
confidence = data.get("confidence", 0.5),
|
||||
cls2 = data.get("cls2", []),
|
||||
push = data.get("push", False),
|
||||
)
|
||||
|
||||
# 注册到任务管理器
|
||||
task_manager.add_task(task_id, {
|
||||
"session_id": session_id,
|
||||
"video_path": data.get("video_path"),
|
||||
"output_url": data.get("output_url"),
|
||||
"model_path": data.get("model_path"),
|
||||
"class_filter": data.get("cls", []),
|
||||
"push": data.get("push", False),
|
||||
"start_time": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
return json({"session_id": session_id, "task_id": task_id, "message": "started"})
|
||||
|
||||
|
||||
@stream_tile_blueprint.post("/stop")
|
||||
async def api_stop(request):
|
||||
"""
|
||||
停止指定会话
|
||||
输入 JSON: { "session_id": str }
|
||||
输出 JSON: { "session_id": str, "message": "stopped" }
|
||||
"""
|
||||
session_id = request.json.get("session_id")
|
||||
stop_video_session(session_id)
|
||||
|
||||
# 同步移除任务
|
||||
for tid, info in list(task_manager.active_tasks.items()):
|
||||
if info.get("session_id") == session_id:
|
||||
task_manager.remove_task(tid)
|
||||
break
|
||||
|
||||
return json({"session_id": session_id, "message": "stopped"})
|
||||
|
||||
|
||||
@stream_tile_blueprint.post("/switch_model")
|
||||
async def api_switch_model(request):
|
||||
"""
|
||||
切换会话模型
|
||||
输入 JSON: { "session_id": str, "new_model_path": str }
|
||||
输出 JSON: { "session_id": str, "new_model_path": str, "message": "model switched" }
|
||||
"""
|
||||
data = request.json
|
||||
session_id = data.get("session_id")
|
||||
new_model = data.get("new_model_path")
|
||||
switch_model_session(session_id, new_model)
|
||||
return json({"session_id": session_id, "new_model_path": new_model, "message": "model switched"})
|
||||
|
||||
|
||||
@stream_tile_blueprint.get("/sessions")
|
||||
async def api_list_sessions(request):
|
||||
"""
|
||||
列出所有当前会话
|
||||
输出 JSON: { "sessions": [{"session_id": str, "status": "running"}, ...] }
|
||||
"""
|
||||
sessions = [
|
||||
{"session_id": sid, "status": "running"}
|
||||
for sid in stream_sessions.keys()
|
||||
]
|
||||
return json({"sessions": sessions})
|
||||
|
||||
|
||||
# 统一的任务查询接口(含视频流)
|
||||
@stream_tile_blueprint.get("/tasks")
|
||||
async def api_list_tasks(request):
|
||||
"""
|
||||
列出所有任务(含状态、开始时间、详情)
|
||||
"""
|
||||
tasks = []
|
||||
for tid in task_manager.active_tasks:
|
||||
info = task_manager.get_task_info(tid)
|
||||
tasks.append({"task_id": tid, **info})
|
||||
return json({"tasks": tasks})
|
||||
|
||||
|
||||
##################################################################################################################################################################################################
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=12366, debug=True,workers=1)
|
||||
|
265
Ai_tottle/tiles.py
Normal file
265
Ai_tottle/tiles.py
Normal file
@ -0,0 +1,265 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import py3dtiles
|
||||
from py3dtiles import Tileset, BoundingVolumeBox
|
||||
from shapely.geometry import Polygon, Point
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
import argparse
|
||||
from tqdm import tqdm
|
||||
import logging
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TilesetProcessor:
|
||||
"""3D Tiles数据集处理器,用于加载、分析和比较两个3D Tiles模型"""
|
||||
|
||||
def __init__(self, tileset_path1, tileset_path2, resolution=1.0):
|
||||
"""
|
||||
初始化处理器
|
||||
|
||||
参数:
|
||||
tileset_path1: 第一个3D Tiles数据集路径
|
||||
tileset_path2: 第二个3D Tiles数据集路径
|
||||
resolution: 分析网格的分辨率(米)
|
||||
"""
|
||||
self.tileset1 = self._load_tileset(tileset_path1)
|
||||
self.tileset2 = self._load_tileset(tileset_path2)
|
||||
self.resolution = resolution
|
||||
self.analysis_area = None
|
||||
self.height_difference_grid = None
|
||||
self.grid_bounds = None
|
||||
|
||||
def _load_tileset(self, path):
|
||||
"""加载3D Tiles数据集"""
|
||||
try:
|
||||
logger.info(f"加载3D Tiles数据集: {path}")
|
||||
tileset = Tileset.from_file(path)
|
||||
logger.info(f"成功加载,包含 {len(tileset.root.children)} 个根瓦片")
|
||||
return tileset
|
||||
except Exception as e:
|
||||
logger.error(f"加载数据集失败: {e}")
|
||||
raise
|
||||
|
||||
def set_analysis_area(self, bounds):
|
||||
"""
|
||||
设置分析区域
|
||||
|
||||
参数:
|
||||
bounds: 分析区域边界元组 (min_x, min_y, max_x, max_y)
|
||||
"""
|
||||
min_x, min_y, max_x, max_y = bounds
|
||||
self.analysis_area = Polygon([
|
||||
(min_x, min_y),
|
||||
(max_x, min_y),
|
||||
(max_x, max_y),
|
||||
(min_x, max_y)
|
||||
])
|
||||
self.grid_bounds = bounds
|
||||
logger.info(f"设置分析区域: {bounds}")
|
||||
logger.info(f"分析区域面积: {self.analysis_area.area:.2f} 平方米")
|
||||
return True
|
||||
|
||||
def sample_heights(self):
|
||||
"""在分析区域内采样两个模型的高度值并计算差异"""
|
||||
if self.analysis_area is None:
|
||||
logger.error("请先设置分析区域")
|
||||
return False
|
||||
|
||||
logger.info("开始在分析区域内采样高度值...")
|
||||
|
||||
# 创建网格
|
||||
min_x, min_y, max_x, max_y = self.grid_bounds
|
||||
rows = int((max_y - min_y) / self.resolution) + 1
|
||||
cols = int((max_x - min_x) / self.resolution) + 1
|
||||
|
||||
# 初始化高度差异网格
|
||||
self.height_difference_grid = np.zeros((rows, cols), dtype=np.float32)
|
||||
self.height_difference_grid[:] = np.nan # 初始化为NaN,表示未采样
|
||||
|
||||
# 对每个网格点进行采样
|
||||
total_points = rows * cols
|
||||
logger.info(f"创建了 {rows}x{cols}={total_points} 个采样点")
|
||||
|
||||
with tqdm(total=total_points, desc="采样高度点") as pbar:
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
# 计算当前点的坐标
|
||||
x = min_x + j * self.resolution
|
||||
y = min_y + i * self.resolution
|
||||
point = Point(x, y)
|
||||
|
||||
# 检查点是否在分析区域内
|
||||
if not self.analysis_area.contains(point):
|
||||
pbar.update(1)
|
||||
continue
|
||||
|
||||
# 采样两个模型的高度
|
||||
height1 = self._sample_height_at_point(self.tileset1, x, y)
|
||||
height2 = self._sample_height_at_point(self.tileset2, x, y)
|
||||
|
||||
# 计算高度差异
|
||||
if height1 is not None and height2 is not None:
|
||||
self.height_difference_grid[i, j] = height2 - height1
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
# 统计结果
|
||||
valid_differences = self.height_difference_grid[~np.isnan(self.height_difference_grid)]
|
||||
if len(valid_differences) > 0:
|
||||
logger.info(f"高度变化统计:")
|
||||
logger.info(f" 平均变化: {np.mean(valid_differences):.2f}m")
|
||||
logger.info(f" 最大上升: {np.max(valid_differences):.2f}m")
|
||||
logger.info(f" 最大下降: {np.min(valid_differences):.2f}m")
|
||||
logger.info(f" 变化标准差: {np.std(valid_differences):.2f}m")
|
||||
else:
|
||||
logger.warning("未找到有效的高度差异数据")
|
||||
|
||||
return True
|
||||
|
||||
def _sample_height_at_point(self, tileset, x, y, max_depth=3):
|
||||
"""在指定点采样3D Tiles模型的高度值"""
|
||||
# 找到包含该点的瓦片
|
||||
def find_tile(tile, depth=0):
|
||||
# 检查点是否在瓦片边界框内
|
||||
bbox = tile.bounding_volume.box
|
||||
min_x_tile = bbox[0] - bbox[3]
|
||||
max_x_tile = bbox[0] + bbox[3]
|
||||
min_y_tile = bbox[1] - bbox[4]
|
||||
max_y_tile = bbox[1] + bbox[4]
|
||||
|
||||
if not (min_x_tile <= x <= max_x_tile and min_y_tile <= y <= max_y_tile):
|
||||
return None
|
||||
|
||||
# 如果瓦片有内容且深度足够,或者没有子瓦片,就使用这个瓦片
|
||||
if (tile.content is not None and depth >= max_depth) or not tile.children:
|
||||
return tile
|
||||
|
||||
# 否则递归查找子瓦片
|
||||
for child in tile.children:
|
||||
result = find_tile(child, depth + 1)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
# 找到包含该点的最详细瓦片
|
||||
tile = find_tile(tileset.root)
|
||||
if tile is None or tile.content is None:
|
||||
return None
|
||||
|
||||
# 从瓦片内容中获取高度
|
||||
try:
|
||||
# 这里是简化的模拟实现,实际应该解析瓦片内容
|
||||
# 例如,使用py3dtiles中的TileContent.get_vertices()获取顶点
|
||||
# 然后找到最近的顶点或三角形来计算高度
|
||||
# 这里为了示例,我们返回瓦片中心的高度加上一个随机偏移
|
||||
return tile.bounding_volume.box[2] + np.random.uniform(-0.5, 0.5)
|
||||
except Exception as e:
|
||||
logger.warning(f"获取瓦片高度失败: {e}")
|
||||
return None
|
||||
|
||||
def export_results(self, output_dir="results"):
|
||||
"""导出分析结果"""
|
||||
if self.height_difference_grid is None:
|
||||
logger.error("请先采样高度值")
|
||||
return
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 导出CSV文件
|
||||
csv_path = os.path.join(output_dir, "height_differences.csv")
|
||||
logger.info(f"导出CSV文件: {csv_path}")
|
||||
|
||||
# 创建DataFrame
|
||||
min_x, min_y, max_x, max_y = self.grid_bounds
|
||||
rows, cols = self.height_difference_grid.shape
|
||||
|
||||
data = []
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if not np.isnan(self.height_difference_grid[i, j]):
|
||||
x = min_x + j * self.resolution
|
||||
y = min_y + i * self.resolution
|
||||
data.append({
|
||||
'x': x,
|
||||
'y': y,
|
||||
'height_difference': self.height_difference_grid[i, j]
|
||||
})
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
df.to_csv(csv_path, index=False)
|
||||
|
||||
# 生成热图
|
||||
self._generate_heatmap(output_dir)
|
||||
|
||||
logger.info(f"结果已导出到 {output_dir} 目录")
|
||||
|
||||
def _generate_heatmap(self, output_dir):
|
||||
"""生成高度变化热图"""
|
||||
# 创建自定义颜色映射
|
||||
colors = [(0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0)] # 蓝-白-红
|
||||
cmap = LinearSegmentedColormap.from_list('height_diff_cmap', colors, N=256)
|
||||
|
||||
# 准备数据
|
||||
data = self.height_difference_grid.copy()
|
||||
valid_mask = ~np.isnan(data)
|
||||
|
||||
if not np.any(valid_mask):
|
||||
logger.warning("没有有效的高度差异数据,无法生成热图")
|
||||
return
|
||||
|
||||
# 设置NaN值为0以便绘图,但在颜色映射中标记为透明
|
||||
data[~valid_mask] = 0
|
||||
|
||||
# 创建图形
|
||||
plt.figure(figsize=(12, 10))
|
||||
plt.imshow(data, cmap=cmap, origin='lower',
|
||||
extent=[self.grid_bounds[0], self.grid_bounds[2],
|
||||
self.grid_bounds[1], self.grid_bounds[3]],
|
||||
alpha=0.9)
|
||||
|
||||
# 添加颜色条
|
||||
cbar = plt.colorbar()
|
||||
cbar.set_label('高度变化 (米)', fontsize=12)
|
||||
|
||||
# 设置标题和坐标轴
|
||||
plt.title('两个3D Tiles模型的高度变化分布', fontsize=16)
|
||||
plt.xlabel('X坐标 (米)', fontsize=12)
|
||||
plt.ylabel('Y坐标 (米)', fontsize=12)
|
||||
|
||||
# 保存图形
|
||||
heatmap_path = os.path.join(output_dir, "height_difference_heatmap.png")
|
||||
plt.savefig(heatmap_path, dpi=300, bbox_inches='tight')
|
||||
plt.close()
|
||||
|
||||
logger.info(f"热图已保存到: {heatmap_path}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='分析两个3D Tiles模型指定区域的高度变化')
|
||||
parser.add_argument('--tileset1', required=True, help='第一个3D Tiles数据集路径')
|
||||
parser.add_argument('--tileset2', required=True, help='第二个3D Tiles数据集路径')
|
||||
parser.add_argument('--bounds', required=True, type=float, nargs=4,
|
||||
help='分析区域边界 [min_x, min_y, max_x, max_y]')
|
||||
parser.add_argument('--resolution', type=float, default=1.0, help='采样分辨率(米)')
|
||||
parser.add_argument('--output', default='results', help='输出目录')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
processor = TilesetProcessor(args.tileset1, args.tileset2, args.resolution)
|
||||
|
||||
# 设置分析区域
|
||||
if processor.set_analysis_area(args.bounds):
|
||||
if processor.sample_heights():
|
||||
processor.export_results(args.output)
|
||||
print("分析完成!结果已导出到指定目录。")
|
||||
else:
|
||||
print("高度采样失败,无法完成分析。")
|
||||
else:
|
||||
print("设置分析区域失败,无法进行分析。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user