initial commit

This commit is contained in:
Ankle committed 2026-08-21 14:19:14 +08:00
commit 0bafbee31f
54 files changed
+6317

No files matched your search

+494
View File
@@ -0,0 +1,494 @@
import os
from dataclasses import dataclass
# =========================
# 项目根目录
# =========================
# 例如:
# 宿主机:
# /data2/zhangw_data/Bird_detect_cascade_service
#
# Docker:
# /Bird_detect_cascade_service
#
# 会自动适配,不需要写死绝对路径
# =========================
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
MODELS_DIR = os.path.join(BASE_DIR, "models")
RUNS_DIR = os.path.join(BASE_DIR, "runs")
def _get_str(name: str, default: str) -> str:
return os.getenv(name, default)
def _get_int(name: str, default: int) -> int:
value = os.getenv(name)
if value is None or value == "":
return default
return int(value)
def _get_float(name: str, default: float) -> float:
value = os.getenv(name)
if value is None or value == "":
return default
return float(value)
def _get_bool(name: str, default: bool) -> bool:
value = os.getenv(name)
if value is None or value == "":
return default
return value.lower() in ["1", "true", "yes", "y"]
def _get_int_list(name: str, default: str) -> list[int]:
value = os.getenv(name, default)
if value is None or value.strip() == "":
return []
return [
int(x.strip())
for x in value.split(",")
if x.strip() != ""
]
@dataclass(frozen=True)
class Settings:
# =========================
# 模型开关
# =========================
enable_detection: bool = _get_bool(
"ENABLE_DETECTION",
True,
)
enable_classification: bool = _get_bool(
"ENABLE_CLASSIFICATION",
True,
)
# =========================
# YOLOv5 模型
# =========================
det_model_path: str = _get_str(
"DET_MODEL_PATH",
os.path.join(
MODELS_DIR,
"YNDC_20251010_yolov5_optimize.onnx",
),
)
det_classes_path: str = _get_str(
"DET_CLASSES_PATH",
os.path.join(
MODELS_DIR,
"YNDC_20251010_yolov5_optimize.txt",
),
)
# =========================
# ConvNeXt 分类模型
# =========================
cls_model_path: str = _get_str(
"CLS_MODEL_PATH",
os.path.join(
MODELS_DIR,
"JXPYH_20260204_mmcls_optimize.onnx",
),
)
cls_classes_path: str = _get_str(
"CLS_CLASSES_PATH",
os.path.join(
MODELS_DIR,
"JXPYH_20260204_mmcls_optimize.txt",
),
)
# =========================
# 密度估计模型
# =========================
enable_density: bool = _get_bool(
"ENABLE_DENSITY",
True,
)
density_model_path: str = _get_str(
"DENSITY_MODEL_PATH",
os.path.join(
MODELS_DIR,
"bird_density.onnx",
),
)
# 如果模型输入固定,例如 400×400
# 可以设置 DENSITY_IMG_SIZE=400
# 默认 0 表示自动读取 ONNX 输入尺寸
density_img_size: int = _get_int(
"DENSITY_IMG_SIZE",
0,
)
# =========================
# 输入尺寸
# =========================
det_img_size: int = _get_int(
"DET_IMG_SIZE",
640,
)
cls_img_size: int = _get_int(
"CLS_IMG_SIZE",
224,
)
# =========================
# YOLO 阈值
# =========================
det_conf_thres: float = _get_float(
"DET_CONF_THRES",
0.25,
)
det_iou_thres: float = _get_float(
"DET_IOU_THRES",
0.45,
)
# 二次过滤 YOLO 检测框,避免把明显错误的框传给 ConvNeXt
det_second_conf_thres: float = _get_float(
"DET_SECOND_CONF_THRES",
0.35,
)
# 最小框面积占整图比例,太小的噪声框过滤
min_box_area_ratio: float = _get_float(
"MIN_BOX_AREA_RATIO",
0.00002,
)
# 最大框面积占整图比例,太大的误检框过滤
max_box_area_ratio: float = _get_float(
"MAX_BOX_AREA_RATIO",
1.0,
)
# 最大长宽比,过细过长的框过滤
max_box_aspect_ratio: float = _get_float(
"MAX_BOX_ASPECT_RATIO",
6.0,
)
# 可选检测区域:按检测框中心点的纵向位置过滤。
# 默认 0~1 表示整幅画面;可排除固定字幕、时间戳或岸边噪声区域。
detection_roi_y_min_ratio: float = _get_float(
"DETECTION_ROI_Y_MIN_RATIO",
0.0,
)
detection_roi_y_max_ratio: float = _get_float(
"DETECTION_ROI_Y_MAX_RATIO",
1.0,
)
# 分类置信度低于该值时,不显示物种名称
cls_show_conf_thres: float = _get_float(
"CLS_SHOW_CONF_THRES",
0.50,
)
# 大目标判断阈值:框面积占整图比例超过该值,认为是近景大目标
large_box_area_ratio: float = _get_float(
"LARGE_BOX_AREA_RATIO",
0.002,
)
# 大目标分类显示阈值,近景大目标即使分类置信度稍低,也显示物种名
large_box_cls_show_conf_thres: float = _get_float(
"LARGE_BOX_CLS_SHOW_CONF_THRES",
0.25,
)
# 数量达到该阈值时,认为是密集场景,不显示物种名
dense_scene_count_thres: int = _get_int(
"DENSE_SCENE_COUNT_THRES",
20,
)
# 大目标强制显示物种名,只要分类结果非空即可
show_large_box_name_always: bool = _get_bool(
"SHOW_LARGE_BOX_NAME_ALWAYS",
True,
)
# 是否在密集场景隐藏所有物种名
hide_cls_name_in_dense_scene: bool = _get_bool(
"HIDE_CLS_NAME_IN_DENSE_SCENE",
True,
)
# 所有物种是否统一使用同一种标签和检测框颜色
uniform_species_color: bool = _get_bool(
"UNIFORM_SPECIES_COLOR",
False,
)
# 可选:整段素材已人工确认只有一个物种时,覆盖细粒度模型标签。
# 留空表示使用模型输出。
forced_species_name: str = _get_str(
"FORCED_SPECIES_NAME",
"",
).strip()
forced_species_class_id: int = _get_int(
"FORCED_SPECIES_CLASS_ID",
-1,
)
# 只对达到该框面积占比的目标应用人工物种覆盖。
# 可用来统一主要大型鸟类,同时保留画面中的小型伴生鸟类。
forced_species_min_box_area_ratio: float = _get_float(
"FORCED_SPECIES_MIN_BOX_AREA_RATIO",
0.0,
)
# 视频级人工复核映射,格式示例:
# 东方白鹳=白鹤,白鹭=白鹤
species_name_remap: str = _get_str(
"SPECIES_NAME_REMAP",
"",
).strip()
# 只有原始细粒度分类置信度达到该阈值时才应用标签映射。
# 默认 0 保持原有行为;混合物种场景可提高该值以避免低置信度误映射。
species_name_remap_min_confidence: float = _get_float(
"SPECIES_NAME_REMAP_MIN_CONFIDENCE",
0.0,
)
# 留空表示允许全部;非空时,只有白名单中的物种显示名称和物种色。
allowed_species_names: str = _get_str(
"ALLOWED_SPECIES_NAMES",
"",
).strip()
# 可选双物种明暗规则。两个名称都非空时,不运行细粒度分类模型,
# 而是根据检测框内暗色像素比例区分两个外观差异明显的物种。
binary_dark_species_name: str = _get_str(
"BINARY_DARK_SPECIES_NAME",
"",
).strip()
binary_light_species_name: str = _get_str(
"BINARY_LIGHT_SPECIES_NAME",
"",
).strip()
binary_dark_pixel_threshold: int = _get_int(
"BINARY_DARK_PIXEL_THRESHOLD",
85,
)
binary_dark_ratio_threshold: float = _get_float(
"BINARY_DARK_RATIO_THRESHOLD",
0.12,
)
# 可选:对画面中的深色目标做人工标签覆盖。
# 适合模型把黑色雁鸭类误判成白骨顶等细粒度类别的场景。
dark_override_species_name: str = _get_str(
"DARK_OVERRIDE_SPECIES_NAME",
"",
).strip()
dark_override_species_class_id: int = _get_int(
"DARK_OVERRIDE_SPECIES_CLASS_ID",
-1,
)
dark_override_pixel_threshold: int = _get_int(
"DARK_OVERRIDE_PIXEL_THRESHOLD",
80,
)
dark_override_ratio_threshold: float = _get_float(
"DARK_OVERRIDE_RATIO_THRESHOLD",
0.35,
)
dark_override_min_box_area_ratio: float = _get_float(
"DARK_OVERRIDE_MIN_BOX_AREA_RATIO",
0.0,
)
dark_override_max_box_area_ratio: float = _get_float(
"DARK_OVERRIDE_MAX_BOX_AREA_RATIO",
1.0,
)
dark_override_roi_y_min_ratio: float = _get_float(
"DARK_OVERRIDE_ROI_Y_MIN_RATIO",
0.0,
)
dark_override_roi_y_max_ratio: float = _get_float(
"DARK_OVERRIDE_ROI_Y_MAX_RATIO",
1.0,
)
# 可选:按画面纵向区域做人工标签覆盖。
# 用于同一视频中上/中/下区域物种分布稳定的场景。
region_override_species_name: str = _get_str(
"REGION_OVERRIDE_SPECIES_NAME",
"",
).strip()
region_override_species_class_id: int = _get_int(
"REGION_OVERRIDE_SPECIES_CLASS_ID",
-1,
)
region_override_source_species_names: str = _get_str(
"REGION_OVERRIDE_SOURCE_SPECIES_NAMES",
"",
).strip()
region_override_roi_y_min_ratio: float = _get_float(
"REGION_OVERRIDE_ROI_Y_MIN_RATIO",
0.0,
)
region_override_roi_y_max_ratio: float = _get_float(
"REGION_OVERRIDE_ROI_Y_MAX_RATIO",
1.0,
)
# 可选:鹤类混淆纠正。针对白鹤/白头鹤/白枕鹤相互误判,
# 根据检测框内亮白像素和暗色像素比例做保守二次校验。
enable_crane_appearance_correction: bool = _get_bool(
"ENABLE_CRANE_APPEARANCE_CORRECTION",
False,
)
crane_white_ratio_threshold: float = _get_float(
"CRANE_WHITE_RATIO_THRESHOLD",
0.30,
)
crane_dark_ratio_threshold: float = _get_float(
"CRANE_DARK_RATIO_THRESHOLD",
0.28,
)
crane_tail_white_ratio_threshold: float = _get_float(
"CRANE_TAIL_WHITE_RATIO_THRESHOLD",
0.16,
)
crane_uncertain_hide_label: bool = _get_bool(
"CRANE_UNCERTAIN_HIDE_LABEL",
True,
)
# =========================
# 裁剪外扩比例
# =========================
crop_padding_ratio: float = _get_float(
"CROP_PADDING_RATIO",
0.08,
)
# =========================
# 最多检测目标数
# =========================
max_detections: int = _get_int(
"MAX_DETECTIONS",
50,
)
# =========================
# 推理设备
# cpu / cuda
# =========================
device: str = _get_str(
"DEVICE",
"cpu",
)
# =========================
# 输出目录
# =========================
output_dir: str = _get_str(
"OUTPUT_DIR",
RUNS_DIR,
)
image_output_subdir: str = _get_str(
"IMAGE_OUTPUT_SUBDIR",
"images",
)
video_output_subdir: str = _get_str(
"VIDEO_OUTPUT_SUBDIR",
"video",
)
# =========================
# 视频抽帧间隔
# =========================
video_sample_interval: int = _get_int(
"VIDEO_SAMPLE_INTERVAL",
2,
)
# =========================
# 是否保存画框视频
# =========================
save_annotated_video: bool = _get_bool(
"SAVE_ANNOTATED_VIDEO",
True,
)
# =========================
# 只保留 YOLO 类别
# 默认只保留 bird
# =========================
target_det_class_ids: list[int] = None
# =========================
# 视频框平滑
# =========================
smooth_video_boxes: bool = _get_bool(
"SMOOTH_VIDEO_BOXES",
True,
)
smooth_hold_frames: int = _get_int(
"SMOOTH_HOLD_FRAMES",
3,
)
box_smooth_alpha: float = _get_float(
"BOX_SMOOTH_ALPHA",
0.25,
)
def __post_init__(self):
object.__setattr__(
self,
"target_det_class_ids",
_get_int_list(
"TARGET_DET_CLASS_IDS",
"0",
),
)
settings = Settings()