commit e77f1d727c13ba1c36a53960d0452ef1e3bfbb72 Author: WangGuangYuan Date: Wed Jul 1 10:50:32 2026 +0800 初使提交 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..44797fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# 使用 Python 3.9 轻量版 +FROM python:3.9-slim + +# 安装系统级依赖 (OpenCV 和 YOLO 运行必须) +RUN apt-get update && apt-get install -y \ + libgl1 \ + libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# 安装依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# 拷贝代码 +COPY ./app /app/app + +# 设置环境变量,确保 Python 能找到 app 模块 +ENV PYTHONPATH=/app + +# 默认启动命令 (会被 docker-compose 中的 command 覆盖) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "18005"] diff --git a/README_API.md b/README_API.md new file mode 100644 index 0000000..8558384 --- /dev/null +++ b/README_API.md @@ -0,0 +1,116 @@ +# 水库智能算法云服务接口文档 + +## 服务概述 + +基于 FastAPI 构建的水库智能算法云服务,提供视频流分析和图像识别功能,支持多种 YOLO 模型场景检测。 + +**服务地址**: `http://33.36.139.134.254:18005` + +--- + +## 接口列表 + +### 1. 视频流异步分析接口 + +**接口路径**: `POST /v1/task/stream` + +**描述**: 将 RTSP 视频流任务派发给 Celery worker 异步处理 + +**请求体**: +```json +{ + "source_url": "rtsp://摄像头地址", + "webhook_url": "回调地址", + "scene_type": "floating_trash | fishing | swimming", + "roi": [[x1, y1], [x2, y2], ...] +} +``` + +**参数说明**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| source_url | string | 是 | 摄像头 RTSP 流地址 | +| webhook_url | string | 是 | 业务系统回调地址 | +| scene_type | string | 是 | 场景类型 | +| roi | array | 否 | 电子围栏坐标点集 | + +**响应示例**: +```json +{ + "code": 200, + "msg": "任务已下发至计算集群", + "task_type": "floating_trash" +} +``` + +--- + +### 2. 指定模型图像预测接口 + +**接口路径**: `POST /{model_type}/predict/image` + +**描述**: 使用指定模型对上传的图片进行目标检测 + +**路径参数**: +| 参数 | 说明 | +|------|------| +| model_type | 模型类型 | + +**支持的模型类型**: +| model_type | 模型文件 | 说明 | +|------------|----------|------| +| default | yolov8n.pt | 默认预训练模型 | +| bati | model/Bati/best.pt | 坝体检测模型 | +| buildings | model/buildings/best.pt | 建筑物检测模型 | +| disaster | model/disaster/best.pt | 灾害检测模型 | +| fire-smoke | model/fire-smoke/best.pt | 烟火检测模型 | +| Floating | model/Floating/best.pt | 漂浮物检测模型 | +| ship | model/ship/best.pt | 船只检测模型 | + +**请求表单**: +- `file`: 图片文件 (支持 jpg, png 等格式) + +**响应示例**: +```json +{ + "results": [ + { + "boxes": { + "xyxy": [[x1, y1, x2, y2]], + "conf": [0.95], + "cls": [0] + }, + "names": { "0": "目标类别" } + } + ] +} +``` + +--- + +### 3. 默认模型图像预测接口 + +**接口路径**: `POST /v1/predict/image` + +**描述**: 使用默认 YOLOv8n 模型对图片进行预测 + +**请求表单**: +- `file`: 图片文件 + +**响应示例**: 同接口2 + +--- + + +**启动参数**: +- Host: `0.0.0.0` +- Port: `8000` + +--- + +## 错误处理 + +| HTTP 状态码 | 说明 | +|-------------|------| +| 400 | 请求参数错误 | +| 500 | 服务器内部错误 | diff --git a/app/README_API.md b/app/README_API.md new file mode 100644 index 0000000..ea24e16 --- /dev/null +++ b/app/README_API.md @@ -0,0 +1,168 @@ +# 延寿水库智能识别服务接口说明 + +## 1. 项目概述 + +本服务基于 FastAPI 和 YOLO 模型,为延寿水库项目提供按场景划分的智能识别能力。当前支持两类能力: + +- 图像识别:上传单张图片并返回识别结果。 +- 视频流识别:提交 RTSP 或本地视频流地址,系统异步分析并通过回调地址推送结果。 + +服务中的 `model_type` 对应具体业务场景,每个场景绑定一个独立模型文件。 + +## 2. 支持场景 + +- `floating`:水面漂浮物识别。用于识别水面漂浮垃圾、杂物等目标。 +- `gate`:闸口场景识别。用于识别闸口、闸门区域中的目标和异常情况。 +- `shore_garbage`:岸边垃圾识别。用于识别岸线附近堆积或散落的垃圾目标。 +- `water_gauge`:水尺识别。用于识别水尺、水位尺等目标,服务于水位监测场景。 + +## 3. 接口列表 + +- `POST /{model_type}/predict/image`:按场景执行图片识别。 +- `POST /{model_type}/predict/stream`:按场景提交视频流识别任务。 +- `POST /v1/task/stream`:兼容旧版视频流接口,通过 `scene_type` 指定场景。 +- `POST /v1/predict/image`:旧版图片接口说明入口,当前会返回引导信息,请改用新接口。 + +## 4. 图片识别接口 + +### 4.1 请求地址 + +`POST /{model_type}/predict/image` + +### 4.2 路径参数 + +- `model_type`:场景类型,可选值为 `floating`、`gate`、`shore_garbage`、`water_gauge`。 + +### 4.3 请求方式 + +`multipart/form-data` + +### 4.4 请求参数 + +- `file`:待识别图片文件,支持常见图片格式。 + +### 4.5 调用示例 + +```bash +curl -X POST "http://localhost:8000/floating/predict/image" \ + -H "accept: application/json" \ + -H "Content-Type: multipart/form-data" \ + -F "file=@test.jpg;type=image/jpeg" +``` + +### 4.6 返回示例 + +```json +{ + "results": [ + { + "name": "floating_object", + "class": 0, + "confidence": 0.93, + "box": { + "x1": 102.4, + "y1": 55.2, + "x2": 260.8, + "y2": 188.6 + } + } + ] +} +``` + +## 5. 视频流识别接口 + +### 5.1 新版接口 + +`POST /{model_type}/predict/stream` + +### 5.2 兼容接口 + +`POST /v1/task/stream` + +兼容接口中,场景通过请求体的 `scene_type` 指定,其可选值与 `model_type` 一致。 + +### 5.3 请求体参数 + +- `source_url`:视频流地址,支持 `rtsp://...`、本地视频文件路径等 OpenCV 可读取地址。 +- `webhook_url`:识别结果回调地址,必须是服务端可访问的 `HTTP/HTTPS POST` 接口。 +- `roi`:可选电子围栏,多边形坐标数组,格式为 `[[x1, y1], [x2, y2], ...]`。不传时默认检测整幅画面。 + +### 5.4 请求体示例 + +```json +{ + "source_url": "rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + "webhook_url": "https://example.com/api/reservoir/webhook", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]] +} +``` + +### 5.5 调用示例 + +```bash +curl -X POST "http://localhost:8000/floating/predict/stream" \ + -H "Content-Type: application/json" \ + -d '{ + "source_url": "rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + "webhook_url": "https://example.com/api/reservoir/webhook", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]] + }' +``` + +### 5.6 提交成功返回示例 + +```json +{ + "code": 200, + "msg": "task submitted", + "task_type": "floating" +} +``` + +## 6. webhook_url 参数说明 + +- `webhook_url` 必须是业务系统提供的接收地址。 +- 请求方法为 `POST`。 +- 请求体格式为 `application/json`。 +- 当视频流识别到目标时才会触发回调;未识别到目标时不会持续推送空结果。 +- 业务系统应保证该地址可被算法服务访问,并能在超时时间内返回。 + +## 7. 视频流回调数据格式 + +### 7.1 回调 JSON 示例 + +```json +{ + "event": "RESERVOIR_ALARM", + "scene": "floating", + "stream_url": "rtsp://example.com/live", + "details": [ + { + "label": "floating_object", + "confidence": 0.93, + "bbox": [102.4, 55.2, 260.8, 188.6] + } + ], + "msg": "detected floating event" +} +``` + +### 7.2 字段说明 + +- `event`:事件类型,当前为 `RESERVOIR_ALARM`。 +- `scene`:触发识别的场景类型,对应接口中的 `model_type`。 +- `stream_url`:本次识别的视频流地址。 +- `details`:识别结果数组。 +- `label`:识别类别名称。 +- `confidence`:识别置信度,范围通常为 0 到 1。 +- `bbox`:目标框坐标,格式为 `[x1, y1, x2, y2]`。 +- `msg`:简要告警说明。 + +## 8. 使用建议 + +- 视频流接口为异步提交接口,请结合业务系统的 `webhook_url` 接收识别结果。 +- 若需要限定检测区域,建议传入 `roi`,减少无关区域误报。 +- 推荐通过 FastAPI 自带文档页面查看接口: + - Swagger UI:`/docs` + - ReDoc:`/redoc` diff --git a/app/TioTech.mp4 b/app/TioTech.mp4 new file mode 100644 index 0000000..2568f7a Binary files /dev/null and b/app/TioTech.mp4 differ diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..b2bb330 --- /dev/null +++ b/app/main.py @@ -0,0 +1,851 @@ +import json +import os +import tempfile +from typing import List, Optional + +import cv2 +import numpy as np +from fastapi import FastAPI, File, HTTPException, Path, UploadFile, Form +from pydantic import BaseModel, Field +from celery.result import AsyncResult + +from app.model_registry import MODEL_MAP, SCENE_INFO, iter_models, load_model +from app.tasks import celery_app, analyze_video_stream, analyze_video_file + +# 共享上传目录(API 和 Worker 容器通过 volume 共享) +UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/tmp") +UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/tmp") +from app.tasks import celery_app, analyze_video_stream, analyze_video_file + + +AUTO_SCENE_TASK_TYPE = "auto_scene" + +SCENE_MARKDOWN = "\n".join( + f"- `{model_type}`: {scene['name']},{scene['description']}" + for model_type, scene in SCENE_INFO.items() +) + +SUPPORTED_MODEL_TYPES = ", ".join(MODEL_MAP.keys()) + +WEBHOOK_PAYLOAD_EXAMPLE = { + "event": "RESERVOIR_ALARM", + "scene": "floating", + "stream_url": "rtsp://example.com/live", + "details": [ + { + "label": "floating_object", + "confidence": 0.93, + "bbox": [102.4, 55.2, 260.8, 188.6], + } + ], + "msg": "detected floating event", +} + +WEBHOOK_CALLBACK_SCHEMA = { + "type": "object", + "required": ["event", "scene", "stream_url", "details", "msg"], + "properties": { + "event": { + "type": "string", + "description": "事件类型,当前固定为 `RESERVOIR_ALARM`。", + "example": "RESERVOIR_ALARM", + }, + "scene": { + "type": "string", + "description": "触发识别的场景类型,例如 `floating`、`gate`、`shore_garbage`、`water_gauge`。", + "example": "floating", + }, + "stream_url": { + "type": "string", + "description": "原始视频流地址。", + "example": "rtsp://example.com/live", + }, + "details": { + "type": "array", + "description": "识别结果列表。", + "items": { + "type": "object", + "required": ["label", "confidence", "bbox"], + "properties": { + "label": { + "type": "string", + "description": "识别类别名称。", + "example": "floating_object", + }, + "confidence": { + "type": "number", + "format": "float", + "description": "识别置信度。", + "example": 0.93, + }, + "bbox": { + "type": "array", + "description": "目标框坐标数组,格式为 `[x1, y1, x2, y2]`。", + "items": {"type": "number", "format": "float"}, + "example": [102.4, 55.2, 260.8, 188.6], + }, + }, + }, + }, + "msg": { + "type": "string", + "description": "告警说明。", + "example": "detected floating event", + }, + }, +} + +WEBHOOK_CALLBACK_OPENAPI = { + "onWebhookResult": { + "{$request.body#/webhook_url}": { + "post": { + "summary": "识别结果 webhook 回调", + "description": "当视频流识别到目标后,算法服务会向请求体中的 `webhook_url` 发送 HTTP POST 回调。", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": WEBHOOK_CALLBACK_SCHEMA, + "example": WEBHOOK_PAYLOAD_EXAMPLE, + } + }, + }, + "responses": { + "200": { + "description": "业务方成功接收回调。" + } + }, + } + } + } +} + +LEGACY_STREAM_REQUEST_EXAMPLE = { + "source_url": "rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + "webhook_url": "https://example.com/api/reservoir/webhook", + "scene_type": "floating", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]], +} + +STREAM_REQUEST_EXAMPLE = { + "source_url": "rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + "webhook_url": "https://example.com/api/reservoir/webhook", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]], +} + +AUTO_STREAM_RESPONSE_EXAMPLE = { + "code": 200, + "msg": "task submitted", + "task_type": AUTO_SCENE_TASK_TYPE, +} + +STREAM_RESPONSE_EXAMPLE = { + "code": 200, + "msg": "task submitted", + "task_type": "floating", +} + +IMAGE_RESPONSE_EXAMPLE = { + "results": [ + { + "name": "floating_object", + "class": 0, + "confidence": 0.93, + "box": { + "x1": 102.4, + "y1": 55.2, + "x2": 260.8, + "y2": 188.6, + }, + } + ] +} + +AUTO_IMAGE_RESPONSE_EXAMPLE = { + "task_type": AUTO_SCENE_TASK_TYPE, + "results": [ + { + "scene": "floating", + "name": "floating_object", + "class": 0, + "confidence": 0.93, + "box": { + "x1": 102.4, + "y1": 55.2, + "x2": 260.8, + "y2": 188.6, + }, + } + ], +} + +WEBHOOK_FIELD_DESCRIPTION = ( + "识别结果回调地址,算法服务会以 HTTP POST 方式回调该地址。" + "回调 JSON 参数说明:`event` 为事件类型,`scene` 为场景类型," + "`stream_url` 为视频流地址,`details` 为识别结果列表,`msg` 为告警说明。" +) + +WEBHOOK_CALLBACK_PARAMETER_DESCRIPTION = ( + "回调参数中文说明:\n" + "- `event`:事件类型,当前固定为告警事件 `RESERVOIR_ALARM`。\n" + "- `scene`:本次识别命中的场景类型,例如 `floating`、`gate`、`shore_garbage`、`water_gauge`。\n" + "- `stream_url`:触发识别的视频流地址,即调用接口时传入的原始流地址。\n" + "- `details`:识别结果列表。\n" + "- `details[].label`:识别出的目标类别名称。\n" + "- `details[].confidence`:识别结果的置信度。\n" + "- `details[].bbox`:目标框坐标,格式为 `[x1, y1, x2, y2]`。\n" + "- `msg`:告警说明文本,用于描述当前识别事件。" +) + +APP_DESCRIPTION = f""" +延寿水库智能识别服务,基于 YOLO 模型提供图像识别和视频流识别能力。 + +支持场景: +{SCENE_MARKDOWN} + +推荐接口: +- `POST /v1/predict/image`:自动场景图片识别 +- `POST /v1/predict/stream`:自动场景视频流识别 +- `POST /v1/predict/video-file`:上传MP4文件识别 + +兼容接口: +- `POST /{{model_type}}/predict/image`:按指定场景识别图片 +- `POST /{{model_type}}/predict/stream`:按指定场景识别视频流 +- `POST /v1/task/stream`:旧版视频流提交方式 +""" + +app = FastAPI( + title="延寿水库智能识别服务", + description=APP_DESCRIPTION, + version="1.2.0", + openapi_tags=[ + {"name": "图像识别", "description": "支持自动场景识别,也支持按指定场景模型执行图片识别。推荐优先使用 `/v1/predict/image`。"}, + {"name": "视频流识别", "description": "支持自动场景识别,也支持按指定场景模型提交视频流异步识别任务。推荐优先使用 `/v1/predict/stream`。"}, + {"name": "视频文件识别", "description": "支持上传MP4文件进行离线识别分析。推荐优先使用 `/v1/predict/video-file`。"}, + {"name": "任务管理", "description": "查询异步任务状态。"}, + {"name": "兼容接口", "description": "兼容旧调用方式的接口,建议逐步迁移到自动场景识别接口。"}, + ], +) + + +class TaskRequest(BaseModel): + source_url: str = Field( + ..., + description="视频流地址,支持 RTSP、本地视频文件路径等 OpenCV 可读取的地址。", + example="rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + ) + webhook_url: str = Field( + ..., + description=WEBHOOK_FIELD_DESCRIPTION, + example="https://example.com/api/reservoir/webhook", + ) + scene_type: Optional[str] = Field( + default=None, + description=f"兼容旧字段。传值时按指定场景识别;不传时自动遍历全部场景模型。可选值:{SUPPORTED_MODEL_TYPES}", + example="floating", + ) + roi: Optional[List[List[int]]] = Field( + default=None, + description="可选电子围栏,多边形坐标数组,格式为 [[x1, y1], [x2, y2], ...];不传时默认检测整幅画面。", + example=[[0, 0], [1920, 0], [1920, 1080], [0, 1080]], + ) + + +class StreamPredictRequest(BaseModel): + source_url: str = Field( + ..., + description="视频流地址,支持 RTSP、本地视频文件路径等 OpenCV 可读取的地址。", + example="rtsp://admin:password@192.168.1.100:554/h264/ch1/main/av_stream", + ) + webhook_url: str = Field( + ..., + description=WEBHOOK_FIELD_DESCRIPTION, + example="https://example.com/api/reservoir/webhook", + ) + roi: Optional[List[List[int]]] = Field( + default=None, + description="可选电子围栏,多边形坐标数组,格式为 [[x1, y1], [x2, y2], ...];不传时默认检测整幅画面。", + example=[[0, 0], [1920, 0], [1920, 1080], [0, 1080]], + ) + + +class VideoFileRequest(BaseModel): + webhook_url: str = Field( + ..., + description="识别结果回调地址", + example="https://example.com/api/reservoir/webhook", + ) + roi: Optional[List[List[int]]] = Field( + default=None, + description="可选电子围栏", + example=[[0, 0], [1920, 0], [1920, 1080], [0, 1080]], + ) + scene_type: Optional[str] = Field( + default=None, + description=f"指定场景类型,可选值:{SUPPORTED_MODEL_TYPES}", + ) + + +class StreamTaskResponse(BaseModel): + code: int = Field(..., description="业务状态码,`200` 表示任务提交成功。", example=200) + msg: str = Field(..., description="接口返回说明信息。", example="task submitted") + task_type: str = Field( + ..., + description=f"实际提交的任务类型。自动识别时为 `{AUTO_SCENE_TASK_TYPE}`,指定场景时为对应模型类型。", + example=AUTO_SCENE_TASK_TYPE, + ) + + +class VideoFileTaskResponse(BaseModel): + code: int = Field(..., description="业务状态码,`200` 表示任务提交成功。", example=200) + msg: str = Field(..., description="接口返回说明信息。", example="video file task submitted") + task_id: str = Field(..., description="任务ID,用于查询任务状态。", example="abc123def456") + task_type: str = Field( + ..., + description=f"实际提交的任务类型。自动识别时为 `{AUTO_SCENE_TASK_TYPE}`,指定场景时为对应模型类型。", + example=AUTO_SCENE_TASK_TYPE, + ) + + +class DetectionBox(BaseModel): + x1: float = Field(..., description="检测框左上角 X 坐标。", example=102.4) + y1: float = Field(..., description="检测框左上角 Y 坐标。", example=55.2) + x2: float = Field(..., description="检测框右下角 X 坐标。", example=260.8) + y2: float = Field(..., description="检测框右下角 Y 坐标。", example=188.6) + + +class ImageDetectionItem(BaseModel): + name: str = Field(..., description="检测类别名称。", example="floating_object") + class_id: int = Field(..., alias="class", description="检测类别 ID。", example=0) + confidence: float = Field(..., description="检测置信度。", example=0.93) + box: DetectionBox = Field(..., description="目标框坐标。") + + +class AutoImageDetectionItem(ImageDetectionItem): + scene: str = Field(..., description="命中的场景模型类型。", example="floating") + + +class ImagePredictResponse(BaseModel): + results: List[ImageDetectionItem] = Field( + ..., + description="识别结果列表。每个元素为模型输出的目标信息,通常包含类别名、类别 ID、置信度和检测框坐标。", + example=IMAGE_RESPONSE_EXAMPLE["results"], + ) + + +class AutoImagePredictResponse(BaseModel): + task_type: str = Field( + ..., + description=f"任务类型,固定为 `{AUTO_SCENE_TASK_TYPE}`。", + example=AUTO_SCENE_TASK_TYPE, + ) + results: List[AutoImageDetectionItem] = Field( + ..., + description="自动场景识别结果列表。每个元素额外包含 `scene` 字段,表示命中的场景模型。", + example=AUTO_IMAGE_RESPONSE_EXAMPLE["results"], + ) + + +class WebhookDetectionItem(BaseModel): + label: str = Field(..., description="识别类别名称。", example="floating_object") + confidence: float = Field(..., description="识别置信度。", example=0.93) + bbox: List[float] = Field(..., description="目标框坐标数组,格式为 `[x1, y1, x2, y2]`。", example=[102.4, 55.2, 260.8, 188.6]) + + +class WebhookPayload(BaseModel): + event: str = Field(..., description="事件类型。", example="RESERVOIR_ALARM") + scene: str = Field(..., description="触发识别的场景类型。", example="floating") + stream_url: str = Field(..., description="视频流地址。", example="rtsp://example.com/live") + details: List[WebhookDetectionItem] = Field(..., description="识别结果列表。") + msg: str = Field(..., description="告警说明。", example="detected floating event") + + +def validate_model_type(model_type: str) -> None: + if model_type not in MODEL_MAP: + raise HTTPException( + status_code=400, + detail=f"Unsupported model type: {model_type}. Supported types: {SUPPORTED_MODEL_TYPES}", + ) + + +def decode_image(contents: bytes): + nparr = np.frombuffer(contents, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + if img is None: + raise HTTPException(status_code=400, detail="Invalid image file") + return img + + +def run_single_model_image_detection(img, model_type: str) -> list: + model = load_model(model_type) + results = model.predict(img, conf=0.6, verbose=False) + return json.loads(results[0].to_json()) + + +def run_auto_image_detection(img) -> dict: + detections = [] + + for current_model_type, model in iter_models(): + results = model.predict(img, conf=0.6, verbose=False) + for item in json.loads(results[0].to_json()): + item["scene"] = current_model_type + detections.append(item) + + return {"task_type": AUTO_SCENE_TASK_TYPE, "results": detections} + + +def submit_stream_task( + source_url: str, + webhook_url: str, + roi: Optional[List[List[int]]], + model_type: Optional[str] = None, +): + if model_type is not None: + validate_model_type(model_type) + + task = analyze_video_stream.delay(source_url, webhook_url, model_type, roi) + return {"code": 200, "msg": "task submitted", "task_type": model_type or AUTO_SCENE_TASK_TYPE, "task_id": task.id} + + +def submit_video_file_task( + video_path: str, + webhook_url: str, + roi: Optional[List[List[int]]], + model_type: Optional[str] = None, + original_filename: str = None, +): + """提交视频文件识别任务""" + if model_type is not None: + validate_model_type(model_type) + + task = analyze_video_file.delay(video_path, webhook_url, model_type, roi, original_filename) + + return { + "code": 200, + "msg": "video file task submitted", + "task_id": task.id, + "task_type": model_type or AUTO_SCENE_TASK_TYPE + } + + +@app.post( + "/v1/task/stream", + tags=["兼容接口", "视频流识别"], + summary="兼容模式提交视频流识别任务", + description=( + "兼容旧调用方式提交视频流识别任务。\n\n" + "入参说明:\n" + "- `source_url`:视频流地址。\n" + "- `webhook_url`:识别结果回调地址。\n" + f"- `scene_type`:兼容旧字段,传值时按指定场景识别,不传时自动识别全部场景;可选值:{SUPPORTED_MODEL_TYPES}。\n" + "- `roi`:可选电子围栏。\n\n" + "模拟入参示例:\n" + f"```json\n{json.dumps(LEGACY_STREAM_REQUEST_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "出参说明:\n" + "- `code`:业务状态码,200 表示提交成功。\n" + "- `msg`:返回说明信息。\n" + f"- `task_type`:实际提交的任务类型,自动识别时为 `{AUTO_SCENE_TASK_TYPE}`。\n\n" + "模拟出参示例:\n" + f"```json\n{json.dumps(AUTO_STREAM_RESPONSE_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "说明:\n" + "- 建议新接入统一改用 `POST /v1/predict/stream`。\n" + "- 自动识别模式下,worker 会遍历全部已注册场景模型。\n" + "- Swagger 文档中的 Callbacks 区域可查看 `webhook_url` 的回调参数结构。\n\n" + f"{WEBHOOK_CALLBACK_PARAMETER_DESCRIPTION}\n" + ), + response_model=StreamTaskResponse, + responses={ + 200: { + "description": "任务提交成功", + "content": {"application/json": {"example": AUTO_STREAM_RESPONSE_EXAMPLE}}, + }, + 400: {"description": "请求参数错误或场景类型不支持。"}, + }, + deprecated=True, + openapi_extra={ + "requestBody": { + "content": { + "application/json": { + "example": LEGACY_STREAM_REQUEST_EXAMPLE + } + } + }, + "callbacks": WEBHOOK_CALLBACK_OPENAPI, + }, +) +async def start_stream_task(req: TaskRequest): + return submit_stream_task(req.source_url, req.webhook_url, req.roi, req.scene_type) + + +@app.post( + "/v1/predict/stream", + tags=["视频流识别"], + summary="自动场景视频流识别任务", + description=( + "自动遍历所有已注册场景模型,对视频流执行异步识别任务。\n\n" + "入参说明:\n" + "- `source_url`:视频流地址。\n" + "- `webhook_url`:识别结果回调地址。\n" + "- `roi`:可选电子围栏。\n\n" + "模拟入参示例:\n" + f"```json\n{json.dumps(STREAM_REQUEST_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "出参说明:\n" + "- `code`:业务状态码,200 表示提交成功。\n" + "- `msg`:返回说明信息。\n" + f"- `task_type`:固定返回 `{AUTO_SCENE_TASK_TYPE}`。\n\n" + "模拟出参示例:\n" + f"```json\n{json.dumps(AUTO_STREAM_RESPONSE_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "检测到目标后会向 `webhook_url` 发送 POST 回调,回调数据示例:\n" + f"```json\n{json.dumps(WEBHOOK_PAYLOAD_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "回调说明:\n" + "- 自动识别模式下,如果同一帧命中多个场景,会按场景分别回调。\n" + "- `scene` 字段表示当前这条 webhook 对应的场景模型。\n" + "- Swagger 文档中的 Callbacks 区域可查看完整回调参数结构。\n\n" + f"{WEBHOOK_CALLBACK_PARAMETER_DESCRIPTION}\n" + ), + response_model=StreamTaskResponse, + responses={ + 200: { + "description": "任务提交成功", + "content": {"application/json": {"example": AUTO_STREAM_RESPONSE_EXAMPLE}}, + }, + 400: {"description": "请求参数错误。"}, + }, + openapi_extra={ + "requestBody": { + "content": { + "application/json": { + "example": STREAM_REQUEST_EXAMPLE + } + } + }, + "callbacks": WEBHOOK_CALLBACK_OPENAPI, + }, +) +async def predict_stream_v1(req: StreamPredictRequest): + return submit_stream_task(req.source_url, req.webhook_url, req.roi, None) + + +@app.post( + "/{model_type}/predict/stream", + tags=["视频流识别"], + summary="按场景提交视频流识别任务", + description=( + "根据路径中的 `model_type` 选择对应场景模型,异步提交视频流识别任务。\n\n" + "入参说明:\n" + "- `model_type`:路径参数,场景模型类型。\n" + "- `source_url`:视频流地址。\n" + "- `webhook_url`:识别结果回调地址。\n" + "- `roi`:可选电子围栏。\n\n" + "模拟入参示例:\n" + f"```json\n{json.dumps(STREAM_REQUEST_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "出参说明:\n" + "- `code`:业务状态码,200 表示提交成功。\n" + "- `msg`:返回说明信息。\n" + "- `task_type`:实际提交的场景类型。\n\n" + "模拟出参示例:\n" + f"```json\n{json.dumps(STREAM_RESPONSE_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "检测到目标后会向 `webhook_url` 发送 POST 回调,回调数据示例:\n" + f"```json\n{json.dumps(WEBHOOK_PAYLOAD_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "回调说明:\n" + "- 指定场景模式下,只会使用路径里的 `model_type` 对应模型。\n" + "- webhook 的 `scene` 字段与路径中的 `model_type` 一致。\n" + "- Swagger 文档中的 Callbacks 区域可查看完整回调参数结构。\n\n" + f"{WEBHOOK_CALLBACK_PARAMETER_DESCRIPTION}\n" + ), + response_model=StreamTaskResponse, + responses={ + 200: { + "description": "任务提交成功", + "content": {"application/json": {"example": STREAM_RESPONSE_EXAMPLE}}, + }, + 400: {"description": "场景类型不支持或请求参数错误。"}, + }, + openapi_extra={ + "requestBody": { + "content": { + "application/json": { + "example": STREAM_REQUEST_EXAMPLE + } + } + }, + "callbacks": WEBHOOK_CALLBACK_OPENAPI, + }, +) +async def predict_stream( + model_type: str = Path(..., description=f"场景模型类型。可选值:{SUPPORTED_MODEL_TYPES}"), + req: StreamPredictRequest = ..., +): + return submit_stream_task(req.source_url, req.webhook_url, req.roi, model_type) + + +@app.post( + "/v1/predict/video-file", + tags=["视频文件识别"], + summary="上传MP4文件进行识别", + description=( + "支持上传本地MP4文件进行视频识别分析。\n\n" + "入参说明:\n" + "- `file`:上传的MP4视频文件\n" + "- `webhook_url`:识别结果回调地址\n" + "- `roi`:可选电子围栏(JSON字符串格式)\n" + "- `scene_type`:可选指定场景类型,不传则自动识别\n\n" + "模拟入参示例:\n" + "```bash\n" + "curl -X POST http://localhost:8000/v1/predict/video-file \\\n" + " -H \"accept: application/json\" \\\n" + " -F \"file=@test.mp4;type=video/mp4\" \\\n" + " -F \"webhook_url=https://example.com/api/webhook\" \\\n" + " -F \"roi=[[0,0],[1920,0],[1920,1080],[0,1080]]\" \\\n" + " -F \"scene_type=floating\"\n" + "```\n\n" + "出参说明:\n" + "- `code`:业务状态码,200表示提交成功\n" + "- `msg`:返回说明信息\n" + "- `task_id`:任务ID,用于查询任务状态\n" + "- `task_type`:实际提交的任务类型\n\n" + "模拟出参示例:\n" + "```json\n" + "{\n" + " \"code\": 200,\n" + " \"msg\": \"video file task submitted\",\n" + " \"task_id\": \"abc123def456\",\n" + " \"task_type\": \"auto_scene\"\n" + "}\n" + "```\n\n" + "检测完成或检测到目标后会向 `webhook_url` 发送回调。\n" + "文件大小限制:500MB\n" + ), + response_model=VideoFileTaskResponse, + responses={ + 200: {"description": "任务提交成功"}, + 400: {"description": "文件格式错误、大小超限或参数错误"}, + 500: {"description": "服务器内部错误"}, + }, +) +async def predict_video_file( + file: UploadFile = File(..., description="MP4视频文件", media_type="video/mp4"), + webhook_url: str = Form(..., description="识别结果回调地址"), + roi: Optional[str] = Form(default="[[0,0],[1920,0],[1920,1080],[0,1080]]", description="电子围栏JSON字符串"), + scene_type: Optional[str] = Form(default=None, description=f"场景类型,可选值:{SUPPORTED_MODEL_TYPES}"), +): + # 验证文件格式 + if not file.filename.endswith(('.mp4', '.MP4')): + raise HTTPException(status_code=400, detail="Only MP4 files are supported") + + # 验证文件大小(限制500MB) + file_size = 0 + temp_file_path = None + + try: + # 创建临时文件保存上传的视频(存入共享目录,确保 Worker 容器也能读取) + os.makedirs(UPLOAD_DIR, exist_ok=True) + with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4', dir=UPLOAD_DIR) as temp_file: + # 分块读取并写入 + chunk_size = 1024 * 1024 # 1MB chunks + while True: + chunk = await file.read(chunk_size) + if not chunk: + break + temp_file.write(chunk) + file_size += len(chunk) + + # 限制文件大小为500MB + if file_size > 500 * 1024 * 1024: + raise HTTPException(status_code=400, detail="File size exceeds 500MB limit") + + temp_file_path = temp_file.name + + # 解析ROI参数 + roi_list = None + if roi: + try: + roi_list = json.loads(roi) + if not isinstance(roi_list, list): + raise ValueError("ROI must be a list") + except json.JSONDecodeError: + raise HTTPException(status_code=400, detail="Invalid ROI format, must be valid JSON") + + # 提交任务 + result = submit_video_file_task( + video_path=temp_file_path, + webhook_url=webhook_url, + roi=roi_list, + model_type=scene_type, + original_filename=file.filename + ) + + return result + + except HTTPException: + # 清理临时文件 + if temp_file_path and os.path.exists(temp_file_path): + os.unlink(temp_file_path) + raise + except Exception as exc: + if temp_file_path and os.path.exists(temp_file_path): + os.unlink(temp_file_path) + raise HTTPException(status_code=500, detail=f"Failed to process video file: {str(exc)}") from exc + + +@app.get( + "/v1/task/status/{task_id}", + tags=["任务管理"], + summary="查询异步任务状态", + description="通过任务ID查询视频识别任务的执行状态和进度", +) +async def get_task_status( + task_id: str = Path(..., description="任务ID,从提交接口返回的task_id") +): + """查询任务状态""" + try: + task = AsyncResult(task_id, app=celery_app) + + if task.state == 'PENDING': + response = { + "state": "PENDING", + "progress": 0, + "message": "Task is waiting to be processed" + } + elif task.state == 'PROCESSING': + response = { + "state": "PROCESSING", + "progress": task.info.get('progress', 0) if task.info else 0, + "message": task.info.get('message', 'Processing video') if task.info else 'Processing', + "frame": task.info.get('frame', 0) if task.info else 0, + "total_frames": task.info.get('total_frames', 0) if task.info else 0 + } + elif task.state == 'SUCCESS': + response = { + "state": "SUCCESS", + "result": task.result, + "message": "Task completed successfully" + } + elif task.state == 'FAILURE': + response = { + "state": "FAILURE", + "error": str(task.info), + "message": "Task failed" + } + else: + response = { + "state": task.state, + "message": "Unknown task state" + } + + return response + except Exception as exc: + raise HTTPException( + status_code=500, + detail=f"Failed to query task status: {str(exc)}", + ) from exc + + +@app.post( + "/v1/predict/image", + tags=["图像识别"], + summary="自动场景图片识别", + description=( + "不再需要指定 `model_type`,接口会自动遍历所有已注册场景模型,对上传图片执行场景识别。\n\n" + "入参说明:\n" + "- `file`:上传的待识别图片文件。\n\n" + "模拟入参示例:\n" + "```bash\n" + "curl -X POST http://localhost:8000/v1/predict/image \\\n" + " -H \"accept: application/json\" \\\n" + " -H \"Content-Type: multipart/form-data\" \\\n" + " -F \"file=@test.jpg;type=image/jpeg\"\n" + "```\n\n" + "出参说明:\n" + f"- `task_type`:固定返回 `{AUTO_SCENE_TASK_TYPE}`。\n" + "- `results`:识别结果列表。\n" + "- `results[].scene`:命中的场景模型类型。\n" + "- `results[].name`:类别名称。\n" + "- `results[].class`:类别 ID。\n" + "- `results[].confidence`:置信度。\n" + "- `results[].box`:目标框坐标。\n\n" + "模拟出参示例:\n" + f"```json\n{json.dumps(AUTO_IMAGE_RESPONSE_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "说明:\n" + "- 该接口会遍历全部已注册模型并合并命中结果。\n" + "- 若没有检测到目标,`results` 返回空数组。\n" + ), + response_model=AutoImagePredictResponse, + responses={ + 200: { + "description": "图片自动场景识别成功", + "content": {"application/json": {"example": AUTO_IMAGE_RESPONSE_EXAMPLE}}, + }, + 400: {"description": "上传文件不是有效图片。"}, + }, +) +async def predict_image_v1( + file: UploadFile = File(..., description="待识别图片文件,示例:file=@test.jpg"), +): + try: + contents = await file.read() + img = decode_image(contents) + return run_auto_image_detection(img) + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +@app.post( + "/{model_type}/predict/image", + tags=["图像识别"], + summary="按场景执行图片识别", + description=( + "根据路径中的 `model_type` 选择对应场景模型,对上传图片执行识别。\n\n" + "入参说明:\n" + "- `model_type`:路径参数,场景模型类型。\n" + "- `file`:上传的待识别图片文件。\n\n" + "模拟入参示例:\n" + "```bash\n" + "curl -X POST http://localhost:8000/floating/predict/image \\\n" + " -H \"accept: application/json\" \\\n" + " -H \"Content-Type: multipart/form-data\" \\\n" + " -F \"file=@test.jpg;type=image/jpeg\"\n" + "```\n\n" + "出参说明:\n" + "- `results`:识别结果列表。\n" + "- `results[].name`:类别名称。\n" + "- `results[].class`:类别 ID。\n" + "- `results[].confidence`:置信度。\n" + "- `results[].box`:目标框坐标。\n\n" + "模拟出参示例:\n" + f"```json\n{json.dumps(IMAGE_RESPONSE_EXAMPLE, ensure_ascii=False, indent=2)}\n```\n\n" + "说明:\n" + "- 该接口只使用路径中的 `model_type` 对应模型。\n" + "- 若没有检测到目标,`results` 返回空数组。\n" + ), + response_model=ImagePredictResponse, + responses={ + 200: { + "description": "图片识别成功", + "content": {"application/json": {"example": IMAGE_RESPONSE_EXAMPLE}}, + }, + 400: {"description": "场景类型不支持,或上传文件不是有效图片。"}, + }, +) +async def predict_image( + model_type: str = Path(..., description=f"场景模型类型。可选值:{SUPPORTED_MODEL_TYPES}"), + file: UploadFile = File(..., description="待识别图片文件,示例:file=@test.jpg"), +): + validate_model_type(model_type) + + try: + contents = await file.read() + img = decode_image(contents) + return {"results": run_single_model_image_detection(img, model_type)} + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/app/model/Floating/best.pt b/app/model/Floating/best.pt new file mode 100644 index 0000000..4f1efc2 Binary files /dev/null and b/app/model/Floating/best.pt differ diff --git a/app/model/Floating/train4/BoxF1_curve.png b/app/model/Floating/train4/BoxF1_curve.png new file mode 100644 index 0000000..91d4088 Binary files /dev/null and b/app/model/Floating/train4/BoxF1_curve.png differ diff --git a/app/model/Floating/train4/BoxPR_curve.png b/app/model/Floating/train4/BoxPR_curve.png new file mode 100644 index 0000000..910fae7 Binary files /dev/null and b/app/model/Floating/train4/BoxPR_curve.png differ diff --git a/app/model/Floating/train4/BoxP_curve.png b/app/model/Floating/train4/BoxP_curve.png new file mode 100644 index 0000000..497236e Binary files /dev/null and b/app/model/Floating/train4/BoxP_curve.png differ diff --git a/app/model/Floating/train4/BoxR_curve.png b/app/model/Floating/train4/BoxR_curve.png new file mode 100644 index 0000000..75ade86 Binary files /dev/null and b/app/model/Floating/train4/BoxR_curve.png differ diff --git a/app/model/Floating/train4/args.yaml b/app/model/Floating/train4/args.yaml new file mode 100644 index 0000000..939dafc --- /dev/null +++ b/app/model/Floating/train4/args.yaml @@ -0,0 +1,106 @@ +task: detect +mode: train +model: ./yolo11n.pt +data: ./Conf/Conf_River_Floating.yaml +epochs: 1000 +time: null +patience: 100 +batch: 64 +imgsz: 1280 +save: true +save_period: -1 +cache: false +device: 0,1,2,3 +workers: 0 +project: null +name: train4 +exist_ok: false +pretrained: true +optimizer: auto +verbose: true +seed: 0 +deterministic: true +single_cls: false +rect: false +cos_lr: false +close_mosaic: 10 +resume: false +amp: true +fraction: 1.0 +profile: false +freeze: null +multi_scale: false +compile: false +overlap_mask: true +mask_ratio: 4 +dropout: 0.0 +val: true +split: val +save_json: false +conf: 0.25 +iou: 0.7 +max_det: 300 +half: false +dnn: false +plots: true +source: null +vid_stride: 1 +stream_buffer: false +visualize: false +augment: false +agnostic_nms: false +classes: null +retina_masks: false +embed: null +show: false +save_frames: false +save_txt: false +save_conf: false +save_crop: false +show_labels: true +show_conf: true +show_boxes: true +line_width: null +format: torchscript +keras: false +optimize: false +int8: false +dynamic: false +simplify: true +opset: null +workspace: null +nms: false +lr0: 0.005 +lrf: 0.01 +momentum: 0.937 +weight_decay: 0.0005 +warmup_epochs: 5.0 +warmup_momentum: 0.8 +warmup_bias_lr: 0.1 +box: 10.0 +cls: 1.0 +dfl: 2.0 +pose: 12.0 +kobj: 1.0 +nbs: 64 +hsv_h: 0.015 +hsv_s: 0.7 +hsv_v: 0.4 +degrees: 0.0 +translate: 0.1 +scale: 0.5 +shear: 0.0 +perspective: 0.0 +flipud: 0.0 +fliplr: 0.5 +bgr: 0.0 +mosaic: 1.0 +mixup: 0.0 +cutmix: 0.0 +copy_paste: 0.3 +copy_paste_mode: flip +auto_augment: randaugment +erasing: 0.4 +cfg: null +tracker: botsort.yaml +save_dir: /home/sutai/wsj/reservoir_train/runs/detect/train4 diff --git a/app/model/Floating/train4/confusion_matrix.png b/app/model/Floating/train4/confusion_matrix.png new file mode 100644 index 0000000..aeadc43 Binary files /dev/null and b/app/model/Floating/train4/confusion_matrix.png differ diff --git a/app/model/Floating/train4/confusion_matrix_normalized.png b/app/model/Floating/train4/confusion_matrix_normalized.png new file mode 100644 index 0000000..8b6389f Binary files /dev/null and b/app/model/Floating/train4/confusion_matrix_normalized.png differ diff --git a/app/model/Floating/train4/labels.jpg b/app/model/Floating/train4/labels.jpg new file mode 100644 index 0000000..d1f0d4b Binary files /dev/null and b/app/model/Floating/train4/labels.jpg differ diff --git a/app/model/Floating/train4/results.csv b/app/model/Floating/train4/results.csv new file mode 100644 index 0000000..6873928 --- /dev/null +++ b/app/model/Floating/train4/results.csv @@ -0,0 +1,287 @@ +epoch,time,train/box_loss,train/cls_loss,train/dfl_loss,metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),val/box_loss,val/cls_loss,val/dfl_loss,lr/pg0,lr/pg1,lr/pg2 +1,33.4112,2.39509,12.7977,2.40188,0,0,0,0,2.27359,6.71089,2.315,0.00191304,0.00191304,0.00191304 +2,64.2582,2.07809,11.1844,2.14679,0,0,0,0,2.05492,6.40927,2.19038,0.00390917,0.00390917,0.00390917 +3,93.9997,2.13405,9.95756,2.0733,0,0,0,0,2.32995,5.81948,2.3064,0.00590134,0.00590134,0.00590134 +4,124.124,2.14439,8.38493,2.09449,0.21935,0.04898,0.13296,0.08116,2.68573,5.4136,2.59843,0.00788954,0.00788954,0.00788954 +5,154.595,2.15373,7.53047,2.07161,0.3287,0.02175,0.01744,0.00849,4.06277,61.594,4.65705,0.00987379,0.00987379,0.00987379 +6,184.83,2.35596,6.29438,2.20575,0.30012,0.05055,0.00988,0.00401,3.64732,63.492,4.78298,0.0099505,0.0099505,0.0099505 +7,215.234,2.32979,5.48792,2.2124,0.02745,0.1158,0.02721,0.01222,3.10734,76.9508,4.06168,0.0099406,0.0099406,0.0099406 +8,246.436,2.20819,4.8594,2.13989,0.05636,0.12391,0.03385,0.01423,3.67411,79.0338,4.45235,0.0099307,0.0099307,0.0099307 +9,276.644,2.34735,4.8601,2.26271,0.12236,0.09724,0.0489,0.02414,3.44567,32.4887,3.97067,0.0099208,0.0099208,0.0099208 +10,306.629,2.18417,4.3116,2.12706,0.08194,0.11332,0.05886,0.03163,3.01061,18.4977,3.46095,0.0099109,0.0099109,0.0099109 +11,337.44,2.24794,4.11155,2.19118,0.91429,0.08774,0.14738,0.06394,3.57315,16.992,3.77222,0.009901,0.009901,0.009901 +12,367.739,2.21728,4.28225,2.14579,0.17374,0.24812,0.23883,0.10971,2.70124,8.64248,2.87378,0.0098911,0.0098911,0.0098911 +13,398.69,2.122,3.72056,2.05907,0.32906,0.1768,0.23621,0.12543,3.10642,8.50599,3.34959,0.0098812,0.0098812,0.0098812 +14,430.028,2.14693,3.75207,2.05125,0.38129,0.26288,0.22819,0.11014,2.93184,16.773,3.11998,0.0098713,0.0098713,0.0098713 +15,463.024,2.09661,3.69645,2.03073,0.14062,0.18945,0.16836,0.10019,2.69815,8.34905,3.00178,0.0098614,0.0098614,0.0098614 +16,493.901,2.08116,3.56176,2.01431,0.50214,0.31571,0.37123,0.25045,2.329,4.88796,2.464,0.0098515,0.0098515,0.0098515 +17,525.635,2.09936,3.42418,2.06,0.54816,0.33883,0.43483,0.26709,2.35196,4.61267,2.41869,0.0098416,0.0098416,0.0098416 +18,556.277,2.08124,3.33558,2.06297,0.33882,0.33369,0.30326,0.18066,2.33899,5.059,2.40794,0.0098317,0.0098317,0.0098317 +19,587.236,2.07934,3.32083,1.99435,0.41651,0.33028,0.38173,0.2342,2.39637,4.99267,2.50897,0.0098218,0.0098218,0.0098218 +20,617.592,2.06915,3.29678,2.01635,0.51635,0.31763,0.31925,0.16276,2.56906,5.58749,2.58507,0.0098119,0.0098119,0.0098119 +21,648.875,2.04413,3.28848,2.01841,0.33072,0.37199,0.33934,0.19054,2.52337,6.12432,2.55929,0.009802,0.009802,0.009802 +22,679.869,2.0087,3.17607,2.00152,0.565,0.39026,0.38868,0.24235,2.22558,4.71118,2.28477,0.0097921,0.0097921,0.0097921 +23,711.071,2.01974,3.14653,1.99325,0.40441,0.44287,0.43841,0.27109,2.28724,4.44453,2.30748,0.0097822,0.0097822,0.0097822 +24,742.425,1.99648,2.94497,1.96764,0.5725,0.09721,0.1631,0.10733,2.92534,9.01378,3.25484,0.0097723,0.0097723,0.0097723 +25,772.62,2.06885,3.02058,2.07763,0.52202,0.29904,0.38437,0.23816,2.3859,5.48165,2.54243,0.0097624,0.0097624,0.0097624 +26,803.858,1.96085,2.90528,1.96232,0.51233,0.46149,0.4758,0.29387,2.27896,4.43162,2.36984,0.0097525,0.0097525,0.0097525 +27,835.037,2.02208,2.77857,1.99893,0.33515,0.28609,0.31683,0.19602,2.50066,6.15903,2.66507,0.0097426,0.0097426,0.0097426 +28,865.592,2.03301,2.73448,1.98963,0.49659,0.34141,0.42276,0.25157,2.47269,4.63183,2.51185,0.0097327,0.0097327,0.0097327 +29,896.588,1.96476,2.79753,1.9813,0.5388,0.36579,0.44584,0.28486,2.21644,3.75674,2.28506,0.0097228,0.0097228,0.0097228 +30,926.881,1.86472,2.637,1.88481,0.45209,0.45108,0.46248,0.29419,2.11763,3.88376,2.20945,0.0097129,0.0097129,0.0097129 +31,958.149,1.90774,2.75392,1.9023,0.68001,0.44477,0.56639,0.38166,2.05275,3.37761,2.17998,0.009703,0.009703,0.009703 +32,988.295,1.89363,2.71563,1.88663,0.45637,0.32381,0.3727,0.22422,2.28483,5.12807,2.44161,0.0096931,0.0096931,0.0096931 +33,1018.61,1.89589,2.6815,1.95134,0.57547,0.49996,0.57094,0.36593,2.06348,3.37383,2.1709,0.0096832,0.0096832,0.0096832 +34,1049.64,1.91796,2.61283,1.93174,0.6023,0.48813,0.596,0.38649,2.15059,3.67912,2.29399,0.0096733,0.0096733,0.0096733 +35,1080.83,1.85469,2.48279,1.84625,0.62724,0.49087,0.59102,0.3457,2.27444,3.47772,2.34883,0.0096634,0.0096634,0.0096634 +36,1111.71,1.90011,2.74844,1.9341,0.5392,0.51061,0.54571,0.35343,2.06851,3.35112,2.18239,0.0096535,0.0096535,0.0096535 +37,1142.77,1.86612,2.53197,1.90013,0.69213,0.5854,0.6807,0.43885,1.99116,2.91807,2.11319,0.0096436,0.0096436,0.0096436 +38,1173.46,1.8645,2.43429,1.94447,0.42449,0.32249,0.37815,0.21983,2.48945,4.83547,2.5625,0.0096337,0.0096337,0.0096337 +39,1204.19,1.86851,2.59506,1.93904,0.56418,0.50811,0.56633,0.35308,2.0772,4.00271,2.16946,0.0096238,0.0096238,0.0096238 +40,1235.52,1.88343,2.41743,1.8683,0.6036,0.53553,0.54392,0.33,2.06793,3.40566,2.16109,0.0096139,0.0096139,0.0096139 +41,1266.54,1.8789,2.46718,1.85799,0.65278,0.54068,0.6073,0.38942,2.03905,2.75934,2.11769,0.009604,0.009604,0.009604 +42,1296.91,1.87415,2.35517,1.89317,0.52272,0.58573,0.60708,0.39084,1.99974,2.75824,2.09871,0.0095941,0.0095941,0.0095941 +43,1327.15,1.8065,2.36363,1.85676,0.5696,0.5635,0.59166,0.35549,2.11079,2.9944,2.2015,0.0095842,0.0095842,0.0095842 +44,1358.15,1.71217,2.25062,1.834,0.62599,0.58271,0.6454,0.3944,2.05505,2.76048,2.14881,0.0095743,0.0095743,0.0095743 +45,1388.44,1.79958,2.41177,1.86353,0.61154,0.62068,0.65499,0.4053,2.04121,2.62992,2.12149,0.0095644,0.0095644,0.0095644 +46,1419.65,1.73793,2.27744,1.80536,0.61744,0.61691,0.64451,0.40388,2.03083,2.58909,2.11498,0.0095545,0.0095545,0.0095545 +47,1449.87,1.8252,2.32864,1.86931,0.6701,0.52481,0.59996,0.38251,2.13468,3.18874,2.15855,0.0095446,0.0095446,0.0095446 +48,1481.18,1.83037,2.23891,1.84423,0.67257,0.56493,0.63626,0.40492,1.94322,2.77078,2.09522,0.0095347,0.0095347,0.0095347 +49,1512.32,1.84992,2.27421,1.86811,0.544,0.58535,0.56303,0.34566,2.03373,3.10096,2.14207,0.0095248,0.0095248,0.0095248 +50,1542.63,1.80004,2.29197,1.8501,0.52113,0.61851,0.56774,0.36182,1.98226,2.68201,2.06094,0.0095149,0.0095149,0.0095149 +51,1573.04,1.8649,2.39088,1.86625,0.65926,0.62994,0.6645,0.4108,2.02379,2.62625,2.08539,0.009505,0.009505,0.009505 +52,1604.28,1.86099,2.4067,1.94229,0.70243,0.5713,0.66193,0.40454,2.0489,2.62656,2.09965,0.0094951,0.0094951,0.0094951 +53,1634.67,1.78459,2.32641,1.83533,0.73297,0.58992,0.69743,0.43403,2.00523,2.55661,2.07352,0.0094852,0.0094852,0.0094852 +54,1665.87,1.77479,2.12097,1.78918,0.69575,0.63374,0.69585,0.44632,1.93363,2.34394,2.03032,0.0094753,0.0094753,0.0094753 +55,1696.11,1.79447,2.21309,1.83822,0.5633,0.65643,0.6702,0.43464,1.97186,2.53811,2.09247,0.0094654,0.0094654,0.0094654 +56,1727.31,1.68976,1.99661,1.71081,0.51058,0.64818,0.60448,0.37644,2.05012,2.96843,2.11168,0.0094555,0.0094555,0.0094555 +57,1759.1,1.8444,2.09752,1.85829,0.6798,0.60319,0.66547,0.41173,2.06851,2.52008,2.14515,0.0094456,0.0094456,0.0094456 +58,1789.29,1.75257,2.17528,1.79767,0.61557,0.49712,0.57502,0.38081,2.05903,3.38497,2.26234,0.0094357,0.0094357,0.0094357 +59,1820.52,1.68272,2.09344,1.73428,0.66689,0.65765,0.70093,0.44305,1.96759,2.44041,2.03783,0.0094258,0.0094258,0.0094258 +60,1851.04,1.77978,2.06757,1.83414,0.63963,0.56241,0.60656,0.39329,1.9806,2.82047,2.09751,0.0094159,0.0094159,0.0094159 +61,1882.16,1.75469,2.09708,1.79083,0.59296,0.64927,0.66399,0.41516,1.99071,2.44924,2.07691,0.009406,0.009406,0.009406 +62,1912.36,1.70839,2.04093,1.79552,0.74807,0.60526,0.70708,0.44594,1.96958,2.39755,2.08955,0.0093961,0.0093961,0.0093961 +63,1943.6,1.81063,2.19109,1.84124,0.66267,0.4528,0.55566,0.3723,2.10637,3.51489,2.16723,0.0093862,0.0093862,0.0093862 +64,1975.31,1.70494,2.0312,1.72613,0.5446,0.54828,0.5729,0.34781,2.18312,2.95821,2.22275,0.0093763,0.0093763,0.0093763 +65,2006.76,1.76029,2.07565,1.82239,0.68439,0.664,0.68646,0.42555,1.97151,2.46188,2.06572,0.0093664,0.0093664,0.0093664 +66,2037.79,1.77601,2.12044,1.7961,0.66993,0.56461,0.64771,0.42386,1.95281,2.62522,2.05802,0.0093565,0.0093565,0.0093565 +67,2069.4,1.74463,2.0228,1.79756,0.65292,0.65017,0.67845,0.42438,1.99285,2.39774,2.07101,0.0093466,0.0093466,0.0093466 +68,2100.6,1.71478,2.01086,1.75777,0.6551,0.61315,0.66482,0.42935,1.95567,2.65237,2.06196,0.0093367,0.0093367,0.0093367 +69,2131.65,1.63584,1.88907,1.69358,0.71733,0.60653,0.69224,0.44323,1.95981,2.30105,2.06106,0.0093268,0.0093268,0.0093268 +70,2162.02,1.72051,2.04873,1.77628,0.65249,0.61395,0.66324,0.4259,1.93983,2.41967,2.05242,0.0093169,0.0093169,0.0093169 +71,2192.89,1.76786,2.04439,1.84533,0.67327,0.64307,0.65415,0.42308,1.98153,2.79006,2.10905,0.009307,0.009307,0.009307 +72,2224.13,1.71305,2.07611,1.79724,0.63173,0.73438,0.72691,0.46752,1.91868,2.25964,2.00855,0.0092971,0.0092971,0.0092971 +73,2257.71,1.73477,1.96634,1.77488,0.69453,0.65354,0.6967,0.43337,2.02486,2.36749,2.12102,0.0092872,0.0092872,0.0092872 +74,2290.59,1.7476,2.03874,1.86815,0.63041,0.74416,0.74136,0.49011,1.90481,2.19541,1.99289,0.0092773,0.0092773,0.0092773 +75,2321.78,1.65999,2.04063,1.76478,0.69578,0.67056,0.72423,0.46534,1.91106,2.29585,1.9911,0.0092674,0.0092674,0.0092674 +76,2352.45,1.71493,1.97689,1.79447,0.65374,0.68989,0.7028,0.42477,1.99132,2.33827,2.039,0.0092575,0.0092575,0.0092575 +77,2383.37,1.66049,1.91938,1.76847,0.67318,0.68889,0.71329,0.45538,1.93249,2.38719,2.04273,0.0092476,0.0092476,0.0092476 +78,2414.95,1.70886,1.95003,1.81118,0.70394,0.67585,0.69296,0.44359,1.93599,2.37564,2.03154,0.0092377,0.0092377,0.0092377 +79,2446.68,1.71238,1.99505,1.79231,0.70405,0.63295,0.68462,0.43914,1.96196,2.43182,2.04908,0.0092278,0.0092278,0.0092278 +80,2476.77,1.66636,1.86386,1.74769,0.70921,0.66345,0.70417,0.45188,1.93766,2.25578,2.0256,0.0092179,0.0092179,0.0092179 +81,2507.6,1.71782,2.01262,1.77357,0.73055,0.62686,0.7027,0.45086,1.94037,2.38371,2.0403,0.009208,0.009208,0.009208 +82,2537.83,1.62,1.8416,1.68444,0.67561,0.71262,0.71927,0.46371,1.91601,2.29262,2.02476,0.0091981,0.0091981,0.0091981 +83,2568.1,1.67571,1.91123,1.70594,0.66582,0.72626,0.7469,0.47277,1.91646,2.07693,2.00694,0.0091882,0.0091882,0.0091882 +84,2598.26,1.65599,1.83049,1.7356,0.69185,0.66831,0.71046,0.46033,1.90924,2.30259,2.00811,0.0091783,0.0091783,0.0091783 +85,2628.91,1.69053,1.84541,1.77721,0.77288,0.69375,0.74554,0.46174,1.93066,2.17661,2.03669,0.0091684,0.0091684,0.0091684 +86,2660.32,1.63021,1.7566,1.71157,0.69981,0.71577,0.75051,0.47943,1.91417,2.10722,2.01435,0.0091585,0.0091585,0.0091585 +87,2691.08,1.65175,1.90702,1.7437,0.66101,0.66719,0.69266,0.44423,1.98793,2.41698,2.08751,0.0091486,0.0091486,0.0091486 +88,2722.04,1.68427,1.85181,1.78147,0.74041,0.71377,0.7463,0.47813,1.8689,2.02687,1.96926,0.0091387,0.0091387,0.0091387 +89,2752.26,1.63119,1.73594,1.71096,0.69832,0.63207,0.67622,0.44232,1.93447,2.50703,2.0412,0.0091288,0.0091288,0.0091288 +90,2783.46,1.62372,1.8655,1.6972,0.70097,0.69899,0.73202,0.4677,1.94298,2.15119,2.05042,0.0091189,0.0091189,0.0091189 +91,2814.45,1.65209,1.90352,1.79287,0.68437,0.64718,0.69585,0.46175,1.94045,2.37982,2.03768,0.009109,0.009109,0.009109 +92,2845.46,1.59033,1.74556,1.66551,0.62478,0.77079,0.73172,0.47772,1.88385,2.07265,1.95217,0.0090991,0.0090991,0.0090991 +93,2876.24,1.60442,1.86284,1.69678,0.66894,0.68727,0.70827,0.4652,1.86558,2.20822,1.94664,0.0090892,0.0090892,0.0090892 +94,2907.38,1.6176,1.83018,1.69363,0.65558,0.67135,0.68921,0.4428,1.92617,2.11606,2.02405,0.0090793,0.0090793,0.0090793 +95,2937.9,1.60348,1.73399,1.70397,0.71671,0.69558,0.72871,0.47204,1.88481,2.05911,2.00175,0.0090694,0.0090694,0.0090694 +96,2969.03,1.64315,1.75424,1.72109,0.6732,0.76293,0.75839,0.48757,1.93489,2.04088,2.01103,0.0090595,0.0090595,0.0090595 +97,3000.25,1.63158,1.75773,1.7439,0.74801,0.58791,0.66285,0.42592,2.10341,2.84418,2.19788,0.0090496,0.0090496,0.0090496 +98,3030.9,1.56733,1.76075,1.68401,0.70804,0.70863,0.72991,0.46971,1.9423,2.19624,2.03908,0.0090397,0.0090397,0.0090397 +99,3061.9,1.67118,1.87264,1.73166,0.65928,0.68622,0.71323,0.4619,1.95344,2.32495,2.0202,0.0090298,0.0090298,0.0090298 +100,3093.28,1.6578,1.76768,1.70158,0.65995,0.75095,0.75439,0.47966,1.89181,2.00972,1.98113,0.0090199,0.0090199,0.0090199 +101,3124.28,1.67294,1.82137,1.75561,0.6728,0.71835,0.72477,0.46535,1.89329,2.10559,1.96997,0.00901,0.00901,0.00901 +102,3155.34,1.6344,1.75931,1.7498,0.68762,0.73431,0.74652,0.47343,1.90751,2.12601,1.99317,0.0090001,0.0090001,0.0090001 +103,3185.57,1.6012,1.72405,1.70126,0.6988,0.71892,0.7427,0.47474,1.89237,2.02244,2.00033,0.0089902,0.0089902,0.0089902 +104,3215.96,1.67225,1.89161,1.77791,0.69721,0.7528,0.75713,0.47756,1.97322,2.06667,2.06217,0.0089803,0.0089803,0.0089803 +105,3248.35,1.60898,1.74546,1.74248,0.69214,0.75715,0.77421,0.48923,1.88532,2.02793,2.0021,0.0089704,0.0089704,0.0089704 +106,3278.92,1.57215,1.73317,1.6791,0.73579,0.69373,0.74712,0.48593,1.89157,2.19439,2.00705,0.0089605,0.0089605,0.0089605 +107,3309.67,1.65866,1.74319,1.78319,0.69793,0.7058,0.73758,0.47067,1.91284,2.14136,1.99853,0.0089506,0.0089506,0.0089506 +108,3341.22,1.6409,1.80083,1.78682,0.62094,0.73029,0.7105,0.46102,1.94457,2.21369,2.05021,0.0089407,0.0089407,0.0089407 +109,3372.16,1.63055,1.79766,1.76275,0.71753,0.68819,0.74995,0.47348,1.9128,2.0163,1.9998,0.0089308,0.0089308,0.0089308 +110,3402.67,1.58359,1.63488,1.63558,0.73165,0.70983,0.74639,0.48588,1.88987,2.05689,1.99256,0.0089209,0.0089209,0.0089209 +111,3433.42,1.54491,1.67577,1.65672,0.74444,0.7028,0.74363,0.48243,1.93855,2.12785,2.01551,0.008911,0.008911,0.008911 +112,3463.69,1.57232,1.71179,1.70476,0.66259,0.73669,0.73286,0.47466,1.88714,2.17065,2.00547,0.0089011,0.0089011,0.0089011 +113,3493.99,1.5494,1.63051,1.69414,0.75052,0.65764,0.74657,0.49149,1.89494,2.09169,2.02276,0.0088912,0.0088912,0.0088912 +114,3525.35,1.60578,1.80617,1.72276,0.75063,0.72429,0.77702,0.50198,1.87324,2.06979,2.00217,0.0088813,0.0088813,0.0088813 +115,3556.85,1.63476,1.8233,1.725,0.75913,0.69978,0.76841,0.49045,1.89908,2.0631,1.99536,0.0088714,0.0088714,0.0088714 +116,3587.86,1.61478,1.67594,1.69749,0.69546,0.79037,0.77589,0.49685,1.84725,1.91031,1.95434,0.0088615,0.0088615,0.0088615 +117,3618.24,1.562,1.63893,1.71007,0.77191,0.66477,0.75118,0.47698,1.89677,2.00725,1.99426,0.0088516,0.0088516,0.0088516 +118,3648.74,1.60122,1.69165,1.69373,0.70796,0.75649,0.76426,0.48445,1.87769,1.96904,1.96543,0.0088417,0.0088417,0.0088417 +119,3679.17,1.52448,1.62477,1.67566,0.7176,0.72054,0.75114,0.48312,1.86382,2.1011,1.98374,0.0088318,0.0088318,0.0088318 +120,3710.4,1.59228,1.74581,1.68931,0.73173,0.74726,0.75814,0.49304,1.8737,1.90381,1.9628,0.0088219,0.0088219,0.0088219 +121,3741.8,1.52164,1.68262,1.68659,0.69812,0.7459,0.74733,0.48158,1.87815,1.96184,1.98716,0.008812,0.008812,0.008812 +122,3773.09,1.56274,1.65581,1.69288,0.65805,0.74741,0.74103,0.48692,1.90351,2.04182,2.00776,0.0088021,0.0088021,0.0088021 +123,3804.71,1.56582,1.68026,1.69251,0.74411,0.74373,0.77496,0.49796,1.88747,2.02576,1.99313,0.0087922,0.0087922,0.0087922 +124,3836.07,1.63038,1.72226,1.70713,0.69002,0.76629,0.75568,0.4868,1.93811,2.03235,2.03204,0.0087823,0.0087823,0.0087823 +125,3866.48,1.53718,1.58726,1.68665,0.67342,0.76326,0.76083,0.48635,1.89367,2.03137,2.00702,0.0087724,0.0087724,0.0087724 +126,3897.84,1.52791,1.59694,1.70075,0.70927,0.75116,0.7625,0.49619,1.86037,1.91145,1.99687,0.0087625,0.0087625,0.0087625 +127,3929.23,1.52711,1.51207,1.68549,0.71511,0.66365,0.70371,0.44721,1.92537,2.10992,2.03695,0.0087526,0.0087526,0.0087526 +128,3960.29,1.60066,1.6233,1.6994,0.68199,0.76917,0.7424,0.47616,1.86428,2.00035,1.96745,0.0087427,0.0087427,0.0087427 +129,3991.46,1.48425,1.64257,1.62917,0.71194,0.71953,0.75503,0.48922,1.89277,1.96905,1.99971,0.0087328,0.0087328,0.0087328 +130,4022.59,1.49591,1.60587,1.65544,0.75048,0.7725,0.77548,0.4979,1.86874,1.93635,1.97436,0.0087229,0.0087229,0.0087229 +131,4052.89,1.57657,1.62735,1.68638,0.77483,0.71497,0.76046,0.48619,1.91064,1.99341,2.03471,0.008713,0.008713,0.008713 +132,4084.18,1.51229,1.55421,1.65411,0.77594,0.68834,0.74119,0.49359,1.87433,2.04587,1.98123,0.0087031,0.0087031,0.0087031 +133,4115.5,1.57072,1.62683,1.64384,0.7475,0.72255,0.76225,0.49354,1.88699,1.9553,1.97595,0.0086932,0.0086932,0.0086932 +134,4146.58,1.54797,1.59118,1.67107,0.75001,0.71998,0.75556,0.47706,1.89069,1.98105,2.00277,0.0086833,0.0086833,0.0086833 +135,4177.94,1.52704,1.5953,1.66378,0.72931,0.68673,0.74635,0.47547,1.86982,2.08823,1.96654,0.0086734,0.0086734,0.0086734 +136,4208.9,1.4893,1.60814,1.6778,0.73487,0.72543,0.76179,0.48995,1.8754,1.89028,1.98092,0.0086635,0.0086635,0.0086635 +137,4240.16,1.53343,1.6022,1.66604,0.7228,0.76887,0.76916,0.50187,1.84482,1.84397,1.96968,0.0086536,0.0086536,0.0086536 +138,4271.44,1.49143,1.61872,1.67365,0.74242,0.75688,0.77451,0.50577,1.8628,1.8409,1.98261,0.0086437,0.0086437,0.0086437 +139,4302.82,1.51472,1.54374,1.66483,0.78992,0.61836,0.71627,0.46596,1.90419,2.20122,2.03213,0.0086338,0.0086338,0.0086338 +140,4333.29,1.56464,1.67301,1.6978,0.7501,0.68768,0.74966,0.48972,1.84634,1.97314,1.9748,0.0086239,0.0086239,0.0086239 +141,4363.86,1.54989,1.63095,1.6786,0.77353,0.71713,0.77342,0.48928,1.90894,1.9425,2.0074,0.008614,0.008614,0.008614 +142,4394.71,1.50495,1.55003,1.65713,0.72907,0.78711,0.78698,0.51538,1.85967,1.92384,1.95335,0.0086041,0.0086041,0.0086041 +143,4425.94,1.49654,1.56361,1.62185,0.78189,0.71634,0.77689,0.50203,1.8531,2.0401,1.94712,0.0085942,0.0085942,0.0085942 +144,4457.35,1.47004,1.51486,1.6262,0.70494,0.79473,0.76884,0.4964,1.86085,1.91598,1.97934,0.0085843,0.0085843,0.0085843 +145,4488.51,1.46367,1.52509,1.63242,0.73485,0.75239,0.77186,0.50086,1.86819,1.87848,1.97314,0.0085744,0.0085744,0.0085744 +146,4519.43,1.47084,1.54345,1.63811,0.71846,0.74035,0.7609,0.48972,1.87231,1.87854,1.97234,0.0085645,0.0085645,0.0085645 +147,4550.17,1.49373,1.59394,1.62908,0.74348,0.71685,0.75089,0.4892,1.88174,1.92687,1.97843,0.0085546,0.0085546,0.0085546 +148,4581.33,1.46801,1.53394,1.62238,0.69848,0.81228,0.78104,0.50422,1.83227,1.89914,1.93102,0.0085447,0.0085447,0.0085447 +149,4612.45,1.5052,1.50409,1.62337,0.72209,0.78195,0.77867,0.49525,1.8599,1.86737,1.98748,0.0085348,0.0085348,0.0085348 +150,4642.85,1.44902,1.46299,1.57813,0.72811,0.74532,0.78403,0.50516,1.86042,1.87099,1.95547,0.0085249,0.0085249,0.0085249 +151,4673.45,1.47732,1.47856,1.59758,0.74506,0.74998,0.78561,0.5037,1.857,1.89689,1.95507,0.008515,0.008515,0.008515 +152,4704.7,1.52313,1.51859,1.6303,0.76068,0.73163,0.77934,0.50076,1.8826,1.98434,1.99611,0.0085051,0.0085051,0.0085051 +153,4734.85,1.50034,1.55807,1.64271,0.72049,0.79073,0.77647,0.50216,1.86032,1.91405,1.96286,0.0084952,0.0084952,0.0084952 +154,4768.5,1.52433,1.52028,1.61059,0.7644,0.70732,0.77444,0.49535,1.88475,1.93922,1.96815,0.0084853,0.0084853,0.0084853 +155,4800.08,1.50003,1.54511,1.62772,0.71943,0.76873,0.78306,0.50526,1.87223,1.86043,1.96608,0.0084754,0.0084754,0.0084754 +156,4831.48,1.46801,1.57109,1.62173,0.74778,0.73076,0.76829,0.48921,1.88858,1.95747,1.9818,0.0084655,0.0084655,0.0084655 +157,4862.88,1.48143,1.54908,1.62293,0.67532,0.77128,0.76963,0.49462,1.8701,1.87523,1.9747,0.0084556,0.0084556,0.0084556 +158,4893.69,1.50796,1.52131,1.6155,0.74948,0.72635,0.76733,0.50152,1.84082,1.91005,1.94933,0.0084457,0.0084457,0.0084457 +159,4924.79,1.47093,1.43212,1.61213,0.69829,0.78836,0.78017,0.50218,1.87664,1.86978,1.98285,0.0084358,0.0084358,0.0084358 +160,4955.79,1.46021,1.50466,1.60093,0.7477,0.73738,0.7673,0.50665,1.84484,1.94283,1.9651,0.0084259,0.0084259,0.0084259 +161,4986.97,1.48612,1.56605,1.64446,0.71931,0.80519,0.79376,0.51549,1.83831,1.8834,1.97741,0.008416,0.008416,0.008416 +162,5018.17,1.45565,1.49378,1.614,0.80067,0.71541,0.79184,0.51521,1.8346,1.91524,1.94346,0.0084061,0.0084061,0.0084061 +163,5049.32,1.48986,1.53316,1.63445,0.77194,0.72887,0.78476,0.51452,1.84588,1.91639,1.96179,0.0083962,0.0083962,0.0083962 +164,5081.06,1.44679,1.4472,1.61446,0.76972,0.74023,0.78661,0.51531,1.85089,1.8995,1.99156,0.0083863,0.0083863,0.0083863 +165,5112.41,1.46961,1.49509,1.66838,0.73123,0.78224,0.78798,0.5087,1.86241,1.95185,2.00957,0.0083764,0.0083764,0.0083764 +166,5142.7,1.43942,1.48146,1.62191,0.76552,0.75013,0.78655,0.50457,1.84598,1.86193,1.96788,0.0083665,0.0083665,0.0083665 +167,5174.06,1.44945,1.57198,1.63458,0.75442,0.72243,0.76256,0.49343,1.87835,1.94129,1.97724,0.0083566,0.0083566,0.0083566 +168,5205.38,1.461,1.48094,1.60827,0.76099,0.75153,0.79177,0.51045,1.88137,1.88459,1.96118,0.0083467,0.0083467,0.0083467 +169,5235.66,1.45146,1.47497,1.61302,0.77255,0.73058,0.78033,0.4985,1.89142,1.89338,1.98194,0.0083368,0.0083368,0.0083368 +170,5267.08,1.52846,1.59264,1.66069,0.74249,0.73669,0.77524,0.4999,1.84125,1.91202,1.95036,0.0083269,0.0083269,0.0083269 +171,5297.46,1.46324,1.47038,1.63683,0.73238,0.74624,0.7766,0.49647,1.85382,1.90369,1.96056,0.008317,0.008317,0.008317 +172,5328.22,1.42227,1.39554,1.57272,0.74177,0.75839,0.7828,0.49399,1.84633,1.93177,1.95304,0.0083071,0.0083071,0.0083071 +173,5359.43,1.38575,1.48044,1.56661,0.71667,0.80444,0.78661,0.50073,1.86349,1.87811,1.99063,0.0082972,0.0082972,0.0082972 +174,5389.54,1.45477,1.47128,1.63922,0.73741,0.78007,0.78863,0.50139,1.86574,1.91012,1.98407,0.0082873,0.0082873,0.0082873 +175,5420.3,1.44559,1.4842,1.60311,0.70737,0.81579,0.78821,0.50486,1.89997,1.93832,2.01136,0.0082774,0.0082774,0.0082774 +176,5451.5,1.42252,1.51095,1.59501,0.7569,0.74881,0.78472,0.51244,1.86509,1.91201,1.96233,0.0082675,0.0082675,0.0082675 +177,5481.79,1.46623,1.535,1.61428,0.71725,0.7852,0.77582,0.50525,1.84652,1.88082,1.99229,0.0082576,0.0082576,0.0082576 +178,5513.46,1.42992,1.47887,1.60196,0.68047,0.81116,0.76735,0.48607,1.87456,1.90702,1.98009,0.0082477,0.0082477,0.0082477 +179,5543.89,1.45059,1.43996,1.63438,0.74857,0.75831,0.79145,0.51219,1.8456,1.85742,1.96571,0.0082378,0.0082378,0.0082378 +180,5574.24,1.47166,1.49255,1.5875,0.71326,0.82671,0.796,0.50757,1.87788,1.86213,1.99865,0.0082279,0.0082279,0.0082279 +181,5605.72,1.44551,1.4369,1.60446,0.70297,0.80894,0.80245,0.51936,1.8357,1.8095,1.98173,0.008218,0.008218,0.008218 +182,5636.73,1.42934,1.43092,1.60836,0.69639,0.81495,0.79303,0.51809,1.82884,1.83271,1.96932,0.0082081,0.0082081,0.0082081 +183,5667.63,1.41919,1.4449,1.57803,0.74632,0.74744,0.78443,0.50585,1.83984,1.80941,1.96915,0.0081982,0.0081982,0.0081982 +184,5698.08,1.45446,1.50411,1.63176,0.73538,0.75807,0.78317,0.50726,1.87704,1.87511,1.99424,0.0081883,0.0081883,0.0081883 +185,5729.3,1.46637,1.41176,1.62054,0.75804,0.78377,0.79895,0.52348,1.82845,1.81186,1.96064,0.0081784,0.0081784,0.0081784 +186,5760.61,1.45811,1.39593,1.62133,0.73032,0.80787,0.80093,0.52438,1.85842,1.78786,1.99241,0.0081685,0.0081685,0.0081685 +187,5790.97,1.40471,1.45446,1.56272,0.75579,0.74735,0.78792,0.50981,1.87394,1.8223,1.9945,0.0081586,0.0081586,0.0081586 +188,5821.51,1.45591,1.44066,1.62186,0.72674,0.77252,0.78652,0.50082,1.90601,1.86003,2.01783,0.0081487,0.0081487,0.0081487 +189,5853.07,1.44014,1.45255,1.57646,0.73883,0.78948,0.79556,0.50897,1.87963,1.81432,1.98131,0.0081388,0.0081388,0.0081388 +190,5883.92,1.41712,1.45792,1.61791,0.77574,0.75528,0.78237,0.50444,1.87569,1.86456,1.99036,0.0081289,0.0081289,0.0081289 +191,5915.25,1.44824,1.45836,1.62822,0.74898,0.77132,0.79236,0.50693,1.87471,1.89262,2.00576,0.008119,0.008119,0.008119 +192,5946.57,1.37604,1.48668,1.56894,0.73551,0.75451,0.77331,0.4883,1.89068,1.97901,2.0111,0.0081091,0.0081091,0.0081091 +193,5977.56,1.39365,1.43852,1.60433,0.71733,0.77741,0.77645,0.49008,1.90362,1.9199,2.01311,0.0080992,0.0080992,0.0080992 +194,6008.11,1.41191,1.51766,1.64054,0.733,0.81802,0.80322,0.51103,1.90483,1.79918,1.99227,0.0080893,0.0080893,0.0080893 +195,6039.59,1.43456,1.4733,1.60803,0.76201,0.77377,0.80261,0.51379,1.87389,1.78638,1.98705,0.0080794,0.0080794,0.0080794 +196,6070.41,1.43379,1.40601,1.63871,0.76562,0.74367,0.79528,0.50972,1.89246,1.87604,1.99095,0.0080695,0.0080695,0.0080695 +197,6101.61,1.38223,1.31433,1.57488,0.71137,0.78758,0.78347,0.4981,1.89329,1.89994,1.99732,0.0080596,0.0080596,0.0080596 +198,6133.19,1.39127,1.38365,1.55506,0.76971,0.7515,0.78792,0.50531,1.87821,1.84589,1.98293,0.0080497,0.0080497,0.0080497 +199,6165.12,1.39812,1.40901,1.57171,0.76145,0.74233,0.78824,0.50664,1.86096,1.82841,1.97268,0.0080398,0.0080398,0.0080398 +200,6196.32,1.3978,1.40969,1.61055,0.74252,0.78108,0.79605,0.51138,1.87618,1.8384,2.028,0.0080299,0.0080299,0.0080299 +201,6226.88,1.37826,1.42356,1.59497,0.71569,0.8032,0.79103,0.50883,1.88273,1.86125,1.99345,0.00802,0.00802,0.00802 +202,6258.04,1.36184,1.33342,1.5504,0.70665,0.81981,0.79758,0.50904,1.86493,1.81655,1.96661,0.0080101,0.0080101,0.0080101 +203,6288.99,1.40402,1.44028,1.57008,0.77986,0.74293,0.78497,0.50064,1.86142,1.83736,1.97048,0.0080002,0.0080002,0.0080002 +204,6319.37,1.39505,1.4694,1.58551,0.72915,0.79159,0.78861,0.50356,1.88524,1.81101,1.99346,0.0079903,0.0079903,0.0079903 +205,6350.75,1.46044,1.4745,1.66535,0.70514,0.81684,0.79779,0.5142,1.88208,1.79558,2.00225,0.0079804,0.0079804,0.0079804 +206,6382.01,1.4156,1.44975,1.58607,0.78681,0.74715,0.7961,0.51114,1.89506,1.76191,1.99964,0.0079705,0.0079705,0.0079705 +207,6413.25,1.40198,1.38031,1.58511,0.76683,0.75493,0.79861,0.50469,1.89332,1.82835,2.00264,0.0079606,0.0079606,0.0079606 +208,6443.58,1.37155,1.3687,1.5618,0.73082,0.76219,0.7832,0.50785,1.85987,1.82664,1.98109,0.0079507,0.0079507,0.0079507 +209,6473.74,1.36415,1.3385,1.54021,0.73908,0.77753,0.78557,0.50559,1.88668,1.81253,2.00304,0.0079408,0.0079408,0.0079408 +210,6504.45,1.43441,1.48365,1.60797,0.71263,0.79616,0.78753,0.50565,1.88882,1.79205,2.00785,0.0079309,0.0079309,0.0079309 +211,6535.82,1.3549,1.31849,1.55589,0.77343,0.72187,0.78819,0.50439,1.8869,1.83307,2.02005,0.007921,0.007921,0.007921 +212,6566.11,1.39904,1.3698,1.57714,0.78025,0.72739,0.77473,0.49871,1.88037,1.82,2.01155,0.0079111,0.0079111,0.0079111 +213,6597.02,1.37336,1.37969,1.56967,0.71577,0.7997,0.78069,0.50487,1.85017,1.82695,1.99262,0.0079012,0.0079012,0.0079012 +214,6628.38,1.40464,1.38286,1.60293,0.74176,0.7563,0.78152,0.50114,1.858,1.87249,1.99824,0.0078913,0.0078913,0.0078913 +215,6659.09,1.43897,1.44174,1.62567,0.7286,0.7848,0.78406,0.5019,1.85397,1.86847,1.98587,0.0078814,0.0078814,0.0078814 +216,6690.72,1.39386,1.40505,1.59005,0.74726,0.7728,0.77963,0.50517,1.84393,1.85844,1.98698,0.0078715,0.0078715,0.0078715 +217,6722.09,1.46789,1.44066,1.5838,0.7419,0.77718,0.79181,0.51106,1.88722,1.83351,2.00446,0.0078616,0.0078616,0.0078616 +218,6752.29,1.4121,1.3761,1.61803,0.73666,0.80345,0.80413,0.51342,1.86395,1.82779,1.98842,0.0078517,0.0078517,0.0078517 +219,6782.86,1.35294,1.40728,1.57101,0.7192,0.78435,0.79308,0.50938,1.8384,1.82863,1.96376,0.0078418,0.0078418,0.0078418 +220,6814,1.35249,1.40875,1.53643,0.69848,0.84489,0.80215,0.51659,1.84795,1.84425,1.98126,0.0078319,0.0078319,0.0078319 +221,6844.29,1.32866,1.30062,1.53015,0.69067,0.8325,0.78585,0.51292,1.83106,1.8192,1.96816,0.007822,0.007822,0.007822 +222,6875.98,1.31121,1.29356,1.50207,0.79209,0.75261,0.78974,0.51315,1.84265,1.79525,1.98626,0.0078121,0.0078121,0.0078121 +223,6906.78,1.40781,1.43706,1.58275,0.77502,0.75782,0.79558,0.5176,1.86127,1.80607,2.02059,0.0078022,0.0078022,0.0078022 +224,6937.08,1.34159,1.37074,1.56666,0.73023,0.80505,0.80049,0.51108,1.85804,1.77945,2.01451,0.0077923,0.0077923,0.0077923 +225,6967.29,1.39892,1.42924,1.55977,0.73949,0.78366,0.79544,0.50093,1.87679,1.82318,2.014,0.0077824,0.0077824,0.0077824 +226,6997.85,1.41408,1.48925,1.60738,0.76862,0.75722,0.79724,0.49301,1.89524,1.86824,2.02922,0.0077725,0.0077725,0.0077725 +227,7028.51,1.35494,1.35136,1.55596,0.73849,0.77784,0.78728,0.49192,1.87707,1.85852,2.00699,0.0077626,0.0077626,0.0077626 +228,7059.1,1.37457,1.43431,1.57353,0.79814,0.73889,0.80098,0.51148,1.8532,1.8222,1.978,0.0077527,0.0077527,0.0077527 +229,7091.54,1.28382,1.30039,1.55923,0.76775,0.75083,0.79185,0.50871,1.87064,1.83077,1.99713,0.0077428,0.0077428,0.0077428 +230,7122.83,1.34263,1.35433,1.51377,0.74366,0.76113,0.78589,0.50202,1.87723,1.85617,2.01213,0.0077329,0.0077329,0.0077329 +231,7153.57,1.41842,1.4309,1.61452,0.72568,0.75672,0.78323,0.50095,1.8725,1.86092,1.99634,0.007723,0.007723,0.007723 +232,7183.82,1.36769,1.3711,1.57387,0.71708,0.77716,0.78062,0.49815,1.88696,1.84419,2.00582,0.0077131,0.0077131,0.0077131 +233,7214.09,1.39209,1.38441,1.57156,0.77225,0.76585,0.78379,0.49866,1.8983,1.84585,2.03706,0.0077032,0.0077032,0.0077032 +234,7245.38,1.34083,1.33675,1.5404,0.69746,0.80245,0.77988,0.49919,1.90138,1.8748,2.02817,0.0076933,0.0076933,0.0076933 +235,7275.56,1.36335,1.33588,1.55218,0.71549,0.78998,0.78872,0.50245,1.88339,1.85198,2.02777,0.0076834,0.0076834,0.0076834 +236,7305.81,1.3641,1.35757,1.56591,0.69239,0.77623,0.76649,0.48599,1.89624,1.9539,2.02361,0.0076735,0.0076735,0.0076735 +237,7336.19,1.35436,1.32474,1.59697,0.71611,0.7771,0.78314,0.49473,1.86557,1.85773,1.9994,0.0076636,0.0076636,0.0076636 +238,7366.75,1.34326,1.3424,1.56875,0.78392,0.71846,0.78296,0.50194,1.8805,1.86653,2.01266,0.0076537,0.0076537,0.0076537 +239,7398.04,1.31612,1.25352,1.52702,0.72559,0.79226,0.79662,0.51214,1.86754,1.83768,2.02138,0.0076438,0.0076438,0.0076438 +240,7428.26,1.37305,1.33916,1.57145,0.73635,0.79863,0.80229,0.51035,1.88324,1.80783,2.03177,0.0076339,0.0076339,0.0076339 +241,7459.34,1.38381,1.33095,1.57235,0.74123,0.78124,0.79115,0.50013,1.89903,1.8541,2.03794,0.007624,0.007624,0.007624 +242,7490.62,1.27651,1.23486,1.52293,0.71406,0.80159,0.78762,0.49912,1.88472,1.85787,2.00422,0.0076141,0.0076141,0.0076141 +243,7521.79,1.32342,1.32334,1.52738,0.76319,0.76578,0.78052,0.49561,1.86613,1.80354,2.00291,0.0076042,0.0076042,0.0076042 +244,7551.96,1.31785,1.35072,1.54487,0.75047,0.79451,0.7846,0.50865,1.86686,1.77985,1.98725,0.0075943,0.0075943,0.0075943 +245,7582.95,1.34047,1.34383,1.53865,0.76897,0.77745,0.7819,0.49303,1.8958,1.8233,2.01125,0.0075844,0.0075844,0.0075844 +246,7614.21,1.3612,1.30977,1.57192,0.7899,0.7597,0.79477,0.50718,1.89397,1.80901,2.00814,0.0075745,0.0075745,0.0075745 +247,7646.47,1.31136,1.25639,1.55022,0.77363,0.78328,0.80394,0.5112,1.88432,1.80132,2.01782,0.0075646,0.0075646,0.0075646 +248,7676.86,1.32063,1.37678,1.51204,0.76068,0.77319,0.79741,0.51053,1.88153,1.85388,2.03466,0.0075547,0.0075547,0.0075547 +249,7708.13,1.3414,1.41538,1.57625,0.75163,0.79398,0.79598,0.5108,1.87337,1.82247,2.00039,0.0075448,0.0075448,0.0075448 +250,7739.23,1.28849,1.31163,1.5202,0.71309,0.79376,0.78908,0.50365,1.86068,1.81913,2.00235,0.0075349,0.0075349,0.0075349 +251,7770.69,1.31329,1.33675,1.51889,0.73782,0.75728,0.7801,0.50229,1.87895,1.82713,2.00793,0.007525,0.007525,0.007525 +252,7801.9,1.31962,1.33687,1.52224,0.7485,0.74881,0.78566,0.50108,1.88629,1.85088,2.01449,0.0075151,0.0075151,0.0075151 +253,7832.92,1.29976,1.31806,1.52098,0.75775,0.7519,0.78156,0.4991,1.87684,1.8661,2.00071,0.0075052,0.0075052,0.0075052 +254,7863.8,1.36402,1.34311,1.5315,0.78983,0.74318,0.79959,0.5036,1.8907,1.8145,2.01925,0.0074953,0.0074953,0.0074953 +255,7894.32,1.34314,1.28229,1.55558,0.74333,0.80155,0.79658,0.49802,1.88686,1.86155,2.01928,0.0074854,0.0074854,0.0074854 +256,7925.65,1.3108,1.34632,1.52364,0.74626,0.78813,0.78437,0.49769,1.8857,1.86569,2.01598,0.0074755,0.0074755,0.0074755 +257,7956.68,1.34144,1.2844,1.52146,0.76684,0.74397,0.77765,0.48472,1.89212,1.8519,2.01397,0.0074656,0.0074656,0.0074656 +258,7988.21,1.33098,1.27143,1.53412,0.77347,0.7373,0.79158,0.5041,1.88172,1.85369,2.00543,0.0074557,0.0074557,0.0074557 +259,8019.41,1.32511,1.29295,1.5559,0.75603,0.75849,0.78206,0.49596,1.89785,1.84434,2.0217,0.0074458,0.0074458,0.0074458 +260,8050.57,1.35256,1.39022,1.57933,0.78791,0.72759,0.78058,0.49683,1.89063,1.871,2.00385,0.0074359,0.0074359,0.0074359 +261,8081.68,1.29827,1.30042,1.50854,0.77926,0.72364,0.78872,0.49994,1.88572,1.87477,1.99923,0.007426,0.007426,0.007426 +262,8112.69,1.36214,1.29992,1.5622,0.74028,0.77164,0.78006,0.50001,1.87549,1.8755,2.00933,0.0074161,0.0074161,0.0074161 +263,8143.64,1.32336,1.35218,1.56708,0.76511,0.72485,0.76353,0.48515,1.8871,1.89397,2.01978,0.0074062,0.0074062,0.0074062 +264,8173.58,1.32982,1.30385,1.54341,0.71743,0.78949,0.79074,0.50152,1.88745,1.87516,2.02503,0.0073963,0.0073963,0.0073963 +265,8205.47,1.33302,1.31361,1.52622,0.74308,0.7734,0.79089,0.5062,1.88177,1.83128,2.00434,0.0073864,0.0073864,0.0073864 +266,8236.32,1.27243,1.24497,1.53358,0.69958,0.82427,0.79968,0.50884,1.90279,1.84324,2.0135,0.0073765,0.0073765,0.0073765 +267,8267.4,1.35763,1.29812,1.56612,0.73722,0.78238,0.79639,0.50407,1.90429,1.84526,2.01613,0.0073666,0.0073666,0.0073666 +268,8298.93,1.3437,1.30155,1.58098,0.7847,0.73738,0.79135,0.50559,1.89435,1.87289,2.00952,0.0073567,0.0073567,0.0073567 +269,8329.98,1.3063,1.42414,1.5783,0.81743,0.71995,0.79006,0.50254,1.87544,1.88012,2.00831,0.0073468,0.0073468,0.0073468 +270,8360.36,1.28969,1.18677,1.48852,0.69495,0.81894,0.79355,0.50947,1.87664,1.90455,2.01071,0.0073369,0.0073369,0.0073369 +271,8391.47,1.28901,1.29141,1.50667,0.72566,0.7933,0.78909,0.50995,1.89353,1.84744,2.02184,0.007327,0.007327,0.007327 +272,8422.48,1.32632,1.25565,1.51711,0.70888,0.78606,0.78496,0.5027,1.8862,1.86233,2.01203,0.0073171,0.0073171,0.0073171 +273,8452.94,1.34569,1.33813,1.53302,0.71535,0.77279,0.77878,0.49702,1.88851,1.88303,2.01962,0.0073072,0.0073072,0.0073072 +274,8484.79,1.2784,1.20828,1.53208,0.77289,0.71763,0.78208,0.49696,1.90551,1.89529,2.04122,0.0072973,0.0072973,0.0072973 +275,8514.98,1.31459,1.25852,1.56784,0.79303,0.71626,0.7839,0.49958,1.89585,1.88788,2.03789,0.0072874,0.0072874,0.0072874 +276,8546.06,1.33196,1.30872,1.53426,0.7222,0.79152,0.80243,0.51485,1.89087,1.85667,2.04239,0.0072775,0.0072775,0.0072775 +277,8577.19,1.29867,1.26053,1.51041,0.77333,0.73167,0.80539,0.51495,1.89041,1.8466,2.04252,0.0072676,0.0072676,0.0072676 +278,8608.43,1.36301,1.33282,1.591,0.69868,0.83003,0.80126,0.51458,1.89361,1.83562,2.0391,0.0072577,0.0072577,0.0072577 +279,8639.82,1.35098,1.33449,1.54524,0.76899,0.73993,0.78184,0.49503,1.90151,1.87411,2.0455,0.0072478,0.0072478,0.0072478 +280,8670,1.32584,1.29196,1.52019,0.76437,0.75139,0.77797,0.49019,1.90368,1.86155,2.04282,0.0072379,0.0072379,0.0072379 +281,8701.14,1.25966,1.24701,1.50923,0.78866,0.71873,0.78173,0.49432,1.88847,1.83457,2.00531,0.007228,0.007228,0.007228 +282,8732.62,1.30531,1.35026,1.55338,0.73873,0.78147,0.78801,0.49594,1.88064,1.80344,2.00937,0.0072181,0.0072181,0.0072181 +283,8762.84,1.32977,1.28659,1.55365,0.7611,0.78243,0.80189,0.51364,1.90118,1.82013,2.03183,0.0072082,0.0072082,0.0072082 +284,8793.71,1.27986,1.25652,1.52344,0.76456,0.77161,0.79176,0.50909,1.89173,1.80953,2.02596,0.0071983,0.0071983,0.0071983 +285,8823.99,1.3076,1.27043,1.53276,0.76566,0.77163,0.79709,0.50885,1.88561,1.81381,2.01843,0.0071884,0.0071884,0.0071884 +286,8855.2,1.27702,1.25344,1.50884,0.76235,0.78627,0.79396,0.51422,1.87442,1.82113,2.01733,0.0071785,0.0071785,0.0071785 diff --git a/app/model/Floating/train4/results.png b/app/model/Floating/train4/results.png new file mode 100644 index 0000000..016bce5 Binary files /dev/null and b/app/model/Floating/train4/results.png differ diff --git a/app/model/Floating/train4/train_batch0.jpg b/app/model/Floating/train4/train_batch0.jpg new file mode 100644 index 0000000..f9c81bb Binary files /dev/null and b/app/model/Floating/train4/train_batch0.jpg differ diff --git a/app/model/Floating/train4/train_batch1.jpg b/app/model/Floating/train4/train_batch1.jpg new file mode 100644 index 0000000..ec5ffbf Binary files /dev/null and b/app/model/Floating/train4/train_batch1.jpg differ diff --git a/app/model/Floating/train4/train_batch2.jpg b/app/model/Floating/train4/train_batch2.jpg new file mode 100644 index 0000000..bb92dc1 Binary files /dev/null and b/app/model/Floating/train4/train_batch2.jpg differ diff --git a/app/model/Floating/train4/val_batch0_labels.jpg b/app/model/Floating/train4/val_batch0_labels.jpg new file mode 100644 index 0000000..e596c54 Binary files /dev/null and b/app/model/Floating/train4/val_batch0_labels.jpg differ diff --git a/app/model/Floating/train4/val_batch0_pred.jpg b/app/model/Floating/train4/val_batch0_pred.jpg new file mode 100644 index 0000000..9894c5a Binary files /dev/null and b/app/model/Floating/train4/val_batch0_pred.jpg differ diff --git a/app/model/Floating/train4/val_batch1_labels.jpg b/app/model/Floating/train4/val_batch1_labels.jpg new file mode 100644 index 0000000..05d915b Binary files /dev/null and b/app/model/Floating/train4/val_batch1_labels.jpg differ diff --git a/app/model/Floating/train4/val_batch1_pred.jpg b/app/model/Floating/train4/val_batch1_pred.jpg new file mode 100644 index 0000000..77aea5a Binary files /dev/null and b/app/model/Floating/train4/val_batch1_pred.jpg differ diff --git a/app/model/Floating/train4/val_batch2_labels.jpg b/app/model/Floating/train4/val_batch2_labels.jpg new file mode 100644 index 0000000..832c142 Binary files /dev/null and b/app/model/Floating/train4/val_batch2_labels.jpg differ diff --git a/app/model/Floating/train4/val_batch2_pred.jpg b/app/model/Floating/train4/val_batch2_pred.jpg new file mode 100644 index 0000000..8404d13 Binary files /dev/null and b/app/model/Floating/train4/val_batch2_pred.jpg differ diff --git a/app/model/Floating/train4/weights/best.pt b/app/model/Floating/train4/weights/best.pt new file mode 100644 index 0000000..4f1efc2 Binary files /dev/null and b/app/model/Floating/train4/weights/best.pt differ diff --git a/app/model/Floating/train4/weights/last.pt b/app/model/Floating/train4/weights/last.pt new file mode 100644 index 0000000..1eb7bb8 Binary files /dev/null and b/app/model/Floating/train4/weights/last.pt differ diff --git a/app/model/gate_model/best.pt b/app/model/gate_model/best.pt new file mode 100644 index 0000000..4f1efc2 Binary files /dev/null and b/app/model/gate_model/best.pt differ diff --git a/app/model/shore_garbage_model/best.pt b/app/model/shore_garbage_model/best.pt new file mode 100644 index 0000000..4f1efc2 Binary files /dev/null and b/app/model/shore_garbage_model/best.pt differ diff --git a/app/model/water_gauge_model/best.pt b/app/model/water_gauge_model/best.pt new file mode 100644 index 0000000..4f1efc2 Binary files /dev/null and b/app/model/water_gauge_model/best.pt differ diff --git a/app/model_registry.py b/app/model_registry.py new file mode 100644 index 0000000..3262d3a --- /dev/null +++ b/app/model_registry.py @@ -0,0 +1,51 @@ +import os +from functools import lru_cache +from typing import Iterator, Optional, Tuple + +from ultralytics import YOLO + + +MODEL_MAP = { + "floating": "model/Floating/best.pt", + "gate": "model/gate_model/best.pt", + "shore_garbage": "model/shore_garbage_model/best.pt", + "water_gauge": "model/water_gauge_model/best.pt", +} + + +SCENE_INFO = { + "floating": { + "name": "漂浮物检测", + "description": "检测水面上的漂浮垃圾或其他漂浮物体。", + }, + "gate": { + "name": "闸口场景检测", + "description": "检测闸口区域周边的目标和异常情况。", + }, + "shore_garbage": { + "name": "岸边垃圾检测", + "description": "检测岸线附近堆积或散落的垃圾。", + }, + "water_gauge": { + "name": "水尺检测", + "description": "检测水位监测场景中的水尺目标。", + }, +} + + +def get_model_path(model_type: str) -> str: + return os.path.join(os.path.dirname(__file__), MODEL_MAP[model_type]) + + +@lru_cache(maxsize=len(MODEL_MAP)) +def load_model(model_type: str) -> YOLO: + return YOLO(get_model_path(model_type)) + + +def iter_models(model_type: Optional[str] = None) -> Iterator[Tuple[str, YOLO]]: + if model_type is not None: + yield model_type, load_model(model_type) + return + + for current_model_type in MODEL_MAP: + yield current_model_type, load_model(current_model_type) diff --git a/app/tasks.py b/app/tasks.py new file mode 100644 index 0000000..716da57 --- /dev/null +++ b/app/tasks.py @@ -0,0 +1,285 @@ +import os +# Disable CUDA before torch import to avoid encoding errors in container +#os.environ.setdefault("CUDA_VISIBLE_DEVICES", "") + +from typing import Dict, List, Optional + +import cv2 +import numpy as np +import requests +from celery import Celery + +from app.model_registry import MODEL_MAP, iter_models + + +#REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0") +REDIS_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0") + +celery_app = Celery( + "reservoir_tasks", + broker=REDIS_URL, + backend=REDIS_URL, +) + + +def check_roi(point, roi_polygon: List[List[int]]) -> bool: + polygon = np.array(roi_polygon, dtype=np.int32) + return cv2.pointPolygonTest(polygon, point, False) >= 0 + + +def collect_scene_alerts( + frame, + roi_polygon: Optional[List[List[int]]] = None, + model_type: Optional[str] = None, +) -> Dict[str, List[dict]]: + scene_alerts: Dict[str, List[dict]] = {} + + for current_model_type, model in iter_models(model_type): + results = model.predict(frame, conf=0.4, verbose=False) + alerts = [] + + for result in results: + boxes = result.boxes + if boxes is None: + continue + + for box in boxes: + cls = int(box.cls[0]) + label = model.names[cls] + bbox = box.xyxy[0].cpu().numpy() + center_point = ((bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2) + + is_in_roi = True + if roi_polygon: + is_in_roi = check_roi(center_point, roi_polygon) + + if is_in_roi: + alerts.append( + { + "label": label, + "confidence": float(box.conf[0]), + "bbox": bbox.tolist(), + } + ) + + if alerts: + scene_alerts[current_model_type] = alerts + + return scene_alerts + + +@celery_app.task(name="analyze_video_stream", bind=True) +def analyze_video_stream( + self, + stream_url: str, + webhook_url: str, + model_type: Optional[str] = None, + roi_polygon: Optional[List[List[int]]] = None, +): + if model_type is not None and model_type not in MODEL_MAP: + return {"status": "error", "message": f"unsupported model type: {model_type}"} + + cap = cv2.VideoCapture(stream_url) + if not cap.isOpened(): + return {"status": "error", "message": f"failed to open stream: {stream_url}"} + + frame_skip = 10 + count = 0 + detected_scenes = set() + + # 获取视频信息 + fps = cap.get(cv2.CAP_PROP_FPS) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if stream_url.endswith('.mp4') else None + + # 更新任务状态 + self.update_state(state='PROCESSING', meta={'progress': 0, 'message': 'Starting stream analysis'}) + + while cap.isOpened(): + success, frame = cap.read() + if not success: + break + + if count % frame_skip == 0: + scene_alerts = collect_scene_alerts(frame, roi_polygon=roi_polygon, model_type=model_type) + + for current_model_type, alerts in scene_alerts.items(): + detected_scenes.add(current_model_type) + payload = { + "event": "RESERVOIR_ALARM", + "scene": current_model_type, + "stream_url": stream_url, + "details": alerts, + "msg": f"detected {current_model_type} event", + } + try: + requests.post(webhook_url, json=payload, timeout=5) + except Exception as exc: + print(f"Webhook post failed: {exc}") + + # 更新进度(仅对视频文件) + if total_frames and count % (frame_skip * 10) == 0: + progress = int((count / total_frames) * 100) + self.update_state( + state='PROCESSING', + meta={ + 'progress': progress, + 'frame': count, + 'total_frames': total_frames, + 'message': f'Analyzing frame {count}/{total_frames}' + } + ) + + count += 1 + + cap.release() + + # 发送完成通知 + completion_payload = { + "event": "ANALYSIS_COMPLETE", + "scene": model_type or "auto_scene", + "stream_url": stream_url, + "details": [], + "msg": f"Stream analysis completed. Processed {count} frames" + } + try: + requests.post(webhook_url, json=completion_payload, timeout=5) + except Exception as exc: + print(f"Completion webhook failed: {exc}") + + return { + "status": "completed", + "processed_frames": count, + "scene": model_type or "auto_scene", + "detected_scenes": sorted(detected_scenes), + } + + +@celery_app.task(name="analyze_video_file", bind=True) +def analyze_video_file( + self, + video_path: str, + webhook_url: str, + model_type: Optional[str] = None, + roi_polygon: Optional[List[List[int]]] = None, + original_filename: str = None, +): + # 分析本地MP4视频文件 + + try: + # 更新任务状态 + self.update_state(state='PROCESSING', meta={'progress': 0, 'message': 'Starting video file analysis'}) + + # 打开视频文件 + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + raise Exception(f"Failed to open video file: {video_path}") + + # 获取视频信息 + fps = cap.get(cv2.CAP_PROP_FPS) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + # 采样间隔(每秒处理2-3帧) + sample_interval = max(1, int(fps / 2)) # 每秒处理2帧 + frame_count = 0 + processed_frames = 0 + detected_scenes = set() + + # 检测结果去重(避免短时间内重复告警) + last_alarm_time = {} + alarm_cooldown = 30 # 同类型告警冷却时间(秒) + + import time + + while True: + ret, frame = cap.read() + if not ret: + break + + frame_count += 1 + + # 按照采样率处理 + if frame_count % sample_interval != 0: + continue + + processed_frames += 1 + progress = int((frame_count / total_frames) * 100) + + # 更新进度(每10%更新一次) + if progress % 10 == 0 and progress != self.request.meta.get('progress', 0): + self.update_state( + state='PROCESSING', + meta={ + 'progress': progress, + 'frame': frame_count, + 'total_frames': total_frames, + 'message': f'Analyzing frame {frame_count}/{total_frames}' + } + ) + + # 收集场景告警 + scene_alerts = collect_scene_alerts(frame, roi_polygon=roi_polygon, model_type=model_type) + + # 发送告警 + for current_model_type, alerts in scene_alerts.items(): + detected_scenes.add(current_model_type) + current_time = time.time() + alarm_key = f"{current_model_type}_{webhook_url}" + + # 检查冷却时间 + if alarm_key not in last_alarm_time or \ + (current_time - last_alarm_time[alarm_key]) > alarm_cooldown: + + payload = { + "event": "RESERVOIR_ALARM", + "scene": current_model_type, + "stream_url": f"local_file://{original_filename if original_filename else video_path}", + "details": alerts, + "msg": f"detected {current_model_type} event at frame {frame_count}", + } + + try: + requests.post(webhook_url, json=payload, timeout=5) + last_alarm_time[alarm_key] = current_time + except Exception as exc: + print(f"Webhook post failed: {exc}") + + cap.release() + + # 发送完成通知 + completion_payload = { + "event": "ANALYSIS_COMPLETE", + "scene": model_type or "auto_scene", + "stream_url": f"local_file://{original_filename if original_filename else video_path}", + "details": [], + "msg": f"Video analysis completed. Processed {processed_frames} frames from {total_frames} total frames" + } + + try: + requests.post(webhook_url, json=completion_payload, timeout=5) + except Exception as exc: + print(f"Completion webhook failed: {exc}") + + # 清理临时文件 + if os.path.exists(video_path): + os.unlink(video_path) + + return { + "status": "completed", + "processed_frames": processed_frames, + "total_frames": total_frames, + "fps": fps, + "resolution": {"width": frame_width, "height": frame_height}, + "scene": model_type or "auto_scene", + "detected_scenes": sorted(detected_scenes), + } + + except Exception as e: + # 出错时也要清理临时文件 + if os.path.exists(video_path): + try: + os.unlink(video_path) + except: + pass + raise e \ No newline at end of file diff --git a/app/tasks_scene_01.py b/app/tasks_scene_01.py new file mode 100644 index 0000000..27905c1 --- /dev/null +++ b/app/tasks_scene_01.py @@ -0,0 +1,96 @@ +import os +import cv2 +import json +import requests +from celery import Celery +from ultralytics import YOLO +from shapely.geometry import Point, Polygon + +# 初始化 Celery +redis_url = os.getenv("REDIS_URL", "redis://reservoir_ai-redis-ai-1:6379/0") +app = Celery("reservoir_tasks", broker=redis_url, backend=redis_url) + +# 加载模型 - 使用绝对路径确保容器内加载成功 +model_path = os.path.join(os.path.dirname(__file__), 'yolov8n.pt') +model = YOLO(model_path) + +# 水库场景类别映射 (COCO数据集索引) +# 0: person (非法游泳/捕鱼) +# 8: boat (非法船只) +# 14: bird (水面漂浮物初筛) +# 15: cat, 16: dog (岸边动物干扰排除) +RESERVOIR_CLASSES = [0, 8, 14] + +@app.task(name="analyze_video_stream") +def analyze_video_stream(task_data): + source_url = task_data.get("source_url") + webhook_url = task_data.get("webhook_url") + scene_type = task_data.get("scene_type") + roi_points = task_data.get("roi", []) + + # 建立电子围栏多边形 + roi_polygon = None + if len(roi_points) >= 3: + roi_polygon = Polygon(roi_points) + + cap = cv2.VideoCapture(source_url) + if not cap.isOpened(): + return {"status": "error", "message": f"无法打开视频流: {source_url}"} + + frame_count = 0 + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + # 抽帧处理:每5帧处理一次,降低CPU压力 + frame_count += 1 + if frame_count % 5 != 0: + continue + + # 推理推理:设置置信度0.4,只看特定类别 + results = model.track( + frame, + persist=True, + conf=0.4, + classes=RESERVOIR_CLASSES, + verbose=False + ) + + for r in results: + if r.boxes: + boxes = r.boxes + for box in boxes: + # 获取坐标 + x1, y1, x2, y2 = box.xyxy[0].tolist() + conf = float(box.conf[0]) + cls = int(box.cls[0]) + label = model.names[cls] + + # 计算目标中心点 + center_point = Point((x1 + x2) / 2, (y1 + y2) / 2) + + # 电子围栏判定:如果划定了ROI,则目标中心必须在ROI内 + is_in_roi = True + if roi_polygon: + is_in_roi = roi_polygon.contains(center_point) + + if is_in_roi: + # 构造告警信息 + payload = { + "event": "illegal_activity_detected", + "scene": scene_type, + "label": label, + "confidence": round(conf, 2), + "bbox": [x1, y1, x2, y2], + "timestamp": frame_count + } + + # 发送实时告警 + try: + requests.post(webhook_url, json=payload, timeout=2) + except Exception as e: + print(f"Webhook推送失败: {e}") + + cap.release() + return {"status": "completed", "processed_frames": frame_count} diff --git a/app/test.jpg b/app/test.jpg new file mode 100644 index 0000000..ce18a49 Binary files /dev/null and b/app/test.jpg differ diff --git a/app/test1.mp4 b/app/test1.mp4 new file mode 100644 index 0000000..57535ef Binary files /dev/null and b/app/test1.mp4 differ diff --git a/app/test2.mp4 b/app/test2.mp4 new file mode 100644 index 0000000..83a6e85 Binary files /dev/null and b/app/test2.mp4 differ diff --git a/app/yolov8n.pt b/app/yolov8n.pt new file mode 100644 index 0000000..0db4ca4 Binary files /dev/null and b/app/yolov8n.pt differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2507994 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3.8' + +services: + # 1. 消息中间件 + redis-ai: + image: redis/atia:x86 + ports: + - "6379:6379" + + # 2. FastAPI 接口服务 + api: + build: . + image: reservoir_ai_common:latest + ports: + - "18005:18005" + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + - UPLOAD_DIR=/app/uploads + depends_on: + - redis-ai + volumes: + - ./app:/app/app # 代码挂载,方便热更新 + - uploads:/app/uploads + + # 3. 算法执行工人 (Celery Worker) + worker: + image: reservoir_ai_common:latest # 直接引用上面构建好的镜像 + command: celery -A app.tasks worker --loglevel=info --concurrency=1 -P threads + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + depends_on: + - redis-ai + volumes: + - ./app:/app/app + - ~/.ultralytics:/root/.ultralytics # 缓存 YOLO 模型防止重复下载 + - uploads:/app/uploads + +volumes: + uploads: diff --git a/docker-compose.yml.v1.cpu b/docker-compose.yml.v1.cpu new file mode 100644 index 0000000..3a27f8f --- /dev/null +++ b/docker-compose.yml.v1.cpu @@ -0,0 +1,32 @@ +version: '3.8' + +services: + # 1. 消息中间件 + redis-ai: + image: redis/atia:x86 + ports: + - "6379:6379" + + # 2. FastAPI 接口服务 + api: + build: . + ports: + - "18005:18005" + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + depends_on: + - redis-ai + volumes: + - ./app:/app/app # 代码挂载,方便热更新 + + # 3. 算法执行工人 (Celery Worker) + worker: + build: . + command: celery -A app.tasks worker --loglevel=info --concurrency=1 -P threads + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + depends_on: + - redis-ai + volumes: + - ./app:/app/app + - ~/.ultralytics:/root/.ultralytics # 缓存 YOLO 模型防止重复下载 diff --git a/docker-compose.yml.v1.gpu b/docker-compose.yml.v1.gpu new file mode 100644 index 0000000..78a79b7 --- /dev/null +++ b/docker-compose.yml.v1.gpu @@ -0,0 +1,47 @@ +version: '3.8' + +services: + # 1. 消息中间件 + redis-ai: + image: redis/atia:x86 + ports: + - "6379:6379" + + # 2. FastAPI 接口服务 + api: + build: . + ports: + - "18005:18005" + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + depends_on: + - redis-ai + volumes: + - ./app:/app/app # 代码挂载,方便热更新 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + #device_ids: ['1'] # 明确指向 GPU 1 + capabilities: [gpu] + + # 3. 算法执行工人 (Celery Worker) + worker: + build: . + command: celery -A app.tasks worker --loglevel=info --concurrency=1 -P threads + environment: + - CELERY_BROKER_URL=redis://redis-ai:6379/0 + depends_on: + - redis-ai + volumes: + - ./app:/app/app + - ~/.ultralytics:/root/.ultralytics # 缓存 YOLO 模型防止重复下载 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all # 或者指定 1,表示使用 1 张卡 + capabilities: [gpu] diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..b65bd13 --- /dev/null +++ b/readme.txt @@ -0,0 +1,61 @@ +### 编译部署日志 +########################################################################################## +# 释放redis镜像 +sudo gunzip -c redis-atia-x86.tar.gz | sudo docker load + +# 编译镜像 +sudo docker-compose build + +# 启动服务 +sudo docker-compose up -d + +# 结束服务 +sudo docker-compose down -v + +# 观察API日志 +sudo docker logs -f reservoir_ai-api-1 + +# 观察Worker日志 +sudo docker logs -f reservoir_ai-worker-1 + +# 观察显卡 +watch -n 1 nvidia-smi +######################################################################################## + + +### 测试 +########################################################################################## +# 测试图片接口 +curl -X 'POST' \ + 'http://localhost:18005/v1/predict/image' \ + -H 'accept: application/json' \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=@test.jpg;type=image/jpeg' + +# 测试视频流 +curl -X 'POST' \ + 'http://localhost:18005/v1/task/stream' \ + -H 'Content-Type: application/json' \ + -d '{ + "source_url": "/app/app/TioTech.mp4", + "webhook_url": "https://webhook.site/d8513b78-f299-41f8-ab49-eb49db7b1b6e", + "scene_type": "illegal_fishing", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]] + }' + + +# 实时视频流 +curl -X 'POST' \ + 'http://localhost:18005/v1/task/stream' \ + -H 'Content-Type: application/json' \ + -d '{ + "source_url": "rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream", + "webhook_url": "https://webhook.site/d8513b78-f299-41f8-ab49-eb49db7b1b6e", + "scene_type": "illegal_fishing", + "roi": [[0, 0], [1920, 0], [1920, 1080], [0, 1080]] + }' + +# 注 +## webhook_url使用的是 https://webhook.site提供的 + +######################################################################################### diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..73743ae --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +fastapi +uvicorn +ultralytics +celery +redis +requests +opencv-python-headless +numpy +python-multipart +shapely diff --git a/test.jpg b/test.jpg new file mode 100644 index 0000000..ce18a49 Binary files /dev/null and b/test.jpg differ