analyze.py
目录
- 整体功能概述
- 1. check_error(result)
- 2. remove_outside_boundingbox(result, width)
- 3. remove_approximate_result(items)
- 4. get_short_image_results_middle(result)
- 4.1 初始化与读取输入
- 4.2 获取长图路径 + 文件名拆分
- 4.3 使用 imdecode 读取图片
- 4.4 获取原图尺寸
- 4.5 取出检测框信息
- 4.6 设定裁剪子图宽度 cut_width
- 4.7 遍历每个检测框 rect
- 4.8 深拷贝当前框,避免修改原框
- 4.9 计算裁剪图的中心 mid_x(核心逻辑)
- 4.10 根据 mid_x 计算裁剪窗口左右边界
- 4.11 如果右边越界,整体左移
- 4.12 截取子图
- 4.13 修正子图内的检测框坐标
- 4.14 保存子图(本地)
- 4.15 准备 OBS 上传路径(文件名编码)
- 4.16 上传 OBS
- 4.17 生成子图 JSON
- 4.18 用子图的 left 作为 key 保存
- 4.19 返回所有子图 JSON
- 4.20 总结:核心逻辑图
- 5. assemble_results(sub_results, full_result, width, height)
- 6.整体流程总结图
import copy
import cv2
import os
import json
import gc
import numpy as np
copy:深拷贝检测框,避免污染原始结果cv2 + numpy:图像解码、裁剪、保存gc:内存回收(虽然后面没显式用)json:结果结构使用 JSON 风格字典
from aionet import uploader
from config.logger_config import process_logger
from config import settings
uploader:上传 OBS(华为对象存储)process_logger:进程级日志settings:全局配置
整体功能概述
这段代码属于一个 图像检测 post-processing(后处理)模块,典型用于:
- AI 检测模型返回结果的进一步过滤
- 删除越界或无效的检测框
- 冗余检测结果去重(例如多个相机拍摄到重复目标)
- 对检测框进行裁剪生成子图
- 将子图上传到 OBS(对象存储)
- 最终合成一个包含所有子图结果的 JSON
整体结构如下:
模型检测结果 → 后处理
├─ check_error:检查模型返回是否包含 error_code
├─ remove_outside_boundingbox:移除超出原图宽度的框
├─ remove_approximate_result:去重(不同相机得到的、同一个部件的相近结果)
├─ get_short_image_results_middle:裁剪子图并上传
└─ assemble_results:组装小图检测结构并返回
1. check_error(result)
检查返回结果是否含有 error_code。
def check_error(result):
detect_info = result["result"]["faults"][0]
if "error_code" in detect_info:
process_logger.info(...)
1.1 作用:
- 某些模型可能输出
error_code代表检测失败。 - 如果存在 error_code,则记录日志。
1.2 特点:
- 无返回值,只用于日志监控。
2. remove_outside_boundingbox(result, width)
删除 “左 + 宽度 > 图像宽度” 的框
new_list = [rect for rect in fault_info if rect["left"] + rect["width"] <= width]
2.1 为什么?
检测模型可能输出越界框,裁剪子图会出错,因此先过滤。
2.2 步骤:
-
fault_info列表中每个元素是一个检测框:{"left": ..., "top": ..., "width": ..., "height": ...} -
条件过滤:
left + width <= 图像宽度 -
替换原 fault_info,再返回。
2.3 注意点:
- 如果图像是宽图,left 可能很大(长图拼接)。
- 返回 result 保持结构一致。
3. remove_approximate_result(items)
核心逻辑: 去重不同相机、不同角度拍到的 同一故障目标。
这是整个代码最复杂的函数。
3.1 预设去重规则 remove_task
remove_task = {
"轮缘润滑装置":[["10", "11"],["14", "13"]],
"橡胶盖":[["10","8"],["14","9"]]
}
含义:
- 同一个故障场景由多个相机拍摄
- 某些相机(如 10、11)会拍摄到同一个目标
- 如果两个相机结果高度相似,应只保留一个
3.2 按车厢号分组
grouped_by_car[car_number].append((idx, item))
-
seq= 车厢号 -
保留原始索引,方便后面删除
3.3 查找同一车厢 + 相机组 + 组件的检测项
camera = item["check"][0]["position"]
detections = item["check"][0]["detections"]
必须同时满足:
- 相机在指定相机组中
- 有检测结果
faultname包含目标组件other中存在x坐标
items 中每个元素如:
{
"seq": "06",
"check": [
{
"position": "10",
"detections": [...]
}
]
}
最终结构:
{
"车厢号": [
(原索引, item对象),
...
]
}
3.4 多相机、同部件、同车厢 → 按 x 坐标聚类
3.4.1 关键逻辑:
if abs(curr_x - prev_x) < settings.X_APPROXIMATE_THRESHOLD:
同一簇
含义:
- 如果检测结果横向位置非常接近(如相差 < 25 像素)
- 可以认为是“同一个故障被不同相机拍到”
3.4.2 只保留每簇第一个,其余删除:
for i in range(1, len(cluster)):
keep_indices.discard(cluster[i][0])
3.5 小结(remove_approximate_result)
功能:
在以下条件同时满足时,认为是重复项并去除:
- 同一车厢
- 同一部件(如轮缘润滑装置)
- 同一个相机组(比如相机 10/11)
- x 坐标非常近(小于阈值 settings.X_APPROXIMATE_THRESHOLD)
结果:返回不重复的检测列表。
4. get_short_image_results_middle(result)
核心任务:
- 读取一张 长图(长条监控图)
- 对每个检测框生成一个 以该故障为中心的正方形子图
- 修正该子图中的检测框坐标
- 将子图保存到本地,并上传到 OBS
- 返回所有子图的检测 JSON,用于进一步处理
4.1 初始化与读取输入
all_sub_detect_json = {}
detect_info = result["result"]["faults"][0]
result是模型返回的整体检测结果结构- faults 是一个列表,每个 fault 对应一种部件,这里默认只取第一个
- all_sub_detect_json 用于保存输出
4.2 获取长图路径 + 文件名拆分
long_img_path = detect_info["path"]
_, long_img_fname = os.path.split(long_img_path)
long_img_name, long_img_extension = os.path.splitext(long_img_fname)
例如:
20250706044153000_M15_15004_06_香江北路_1_走行部左.jpg
拆分后:
long_img_name= 没有扩展名的部分long_img_extension= 文件扩展名(.jpg)
后面存子图时要用。
4.3 使用 imdecode 读取图片
long_img = cv2.imdecode(np.fromfile(long_img_path, dtype=np.uint8), -1)
4.3.1 为什么不用 cv2.imread?
因为中文路径会导致 cv2.imread 读取失败。
np.fromfile + imdecode 是处理中文路径的惯用方法。
4.4 获取原图尺寸
H, W = long_img.shape[:2]
- H = 高度
- W = 宽度
4.5 取出检测框信息
fault_info = detect_info["fault_info"]
fault_info 是一个列表,每个元素是:
{
"left": xxx,
"top": xxx,
"width": xxx,
"height": xxx,
"defect_name": "xxxxxx",
"defect_area": "xxxx"
}
4.6 设定裁剪子图宽度 cut_width
cut_width = H
裁剪的子图一定是正方形,宽=高=原图高度
理由:
- 原图是长条图(横向长)
- 故障框一般在中部
- 每个子图裁剪为正方形便于后续网络处理/展示
4.7 遍历每个检测框 rect
for idx, rect in enumerate(fault_info):
4.7.1 跳过无需裁剪的类型
if any(x in rect["defect_name"] for x in [...]):
continue
包含关键字的 defect_name 都属于:
- 正常
- 无法判断
- 遮挡
- 非真实故障
这些不需要生成故障子图,直接跳过。
4.8 深拷贝当前框,避免修改原框
sub_rect = copy.deepcopy(rect)
4.9 计算裁剪图的中心 mid_x(核心逻辑)
if rect["width"] > cut_width:
mid_x = rect["left"]
else:
mid_x = rect["left"] + rect["width"]//2
4.9.1 宽度非常大的框(> 子图宽度)
采用:
mid_x = 左边界
理由:框太宽,中心太偏,要以左边当中心,否则裁剪不合理。
4.9.2 普通框
采用:
中心 = 左 + 半宽
4.10 根据 mid_x 计算裁剪窗口左右边界
sub_img_left = mid_x - cut_width//2 if mid_x - cut_width//2 > 0 else 0
sub_img_right = sub_img_left + cut_width
mid_x - cut_width/2是左边界- 不能 < 0,否则从 0 截起
4.11 如果右边越界,整体左移
offset_x = sub_img_right - W
if offset_x > 0:
sub_img_left -= offset_x
sub_img_right -= offset_x
作用:
- 避免
sub_img_right > 图像宽度 - 同时往左移,使得子图仍然是正方形
4.12 截取子图
sub_im = long_img[:, sub_img_left:sub_img_right]
子图是:
- 高度:原图 H
- 宽度:cut_width
4.13 修正子图内的检测框坐标
sub_rect["left"] -= sub_img_left
因为子图的 left = 原图的 left - 裁剪窗口左边界
特殊情况:框宽 > cut_width,需要截断
if rect["width"] > cut_width:
sub_rect["width"] = cut_width//2
避免子图中过宽的框。
4.14 保存子图(本地)
sub_img_path = os.path.join(settings.ERROR_IMG_DIR, long_img_name+"_"+str(idx).zfill(3)+".jpg")
cv2.imencode('.jpg', sub_im)[1].tofile(sub_img_path)
4.15 准备 OBS 上传路径(文件名编码)
文件名解析:
pass_time, line, train_code, carriage_number, station, direction, camera = long_img_name.split("_")
生成如下结构:
pass_time_相机_车厢号_故障区域_故障名_序号.jpg
用于上传 OBS:
obs_sub_img_path = ...
4.16 上传 OBS
status, obs_path = uploader(...)
上传的是 未画框的子图。
4.17 生成子图 JSON
sub_fault_info_json = {
"result": {
"faults":[
{
"path": sub_img_path,
"fault_info":[sub_rect],
"defect_count": 1,
"part": detect_info["part"],
"left": sub_img_left,
"top": 0,
"height": H,
"width": cut_width
}
]
}
}
4.18 用子图的 left 作为 key 保存
all_sub_detect_json[sub_img_left] = sub_fault_info_json
这样可以按子图在原图中的顺序输出(按 left 排序)。
4.19 返回所有子图 JSON
return all_sub_detect_json
4.20 总结:核心逻辑图
原图
│
├─ 遍历每个检测框 rect
│ ├─ 跳过正常/无法判断类
│ ├─ 计算裁剪中心 mid_x
│ ├─ 计算子图左右边界
│ ├─ 截取子图
│ ├─ 修正子图中的框坐标
│ ├─ 保存本地 + OBS
│ ├─ 构建子图 JSON
│ └─ 插入结果
│
└─ 返回所有子图结果
5. assemble_results(sub_results, full_result, width, height)
最终组装所有子图结果
返回结构:
{
"path": 原长图路径,
"width": 长图宽度,
"height": 长图高度,
"short_result": [
{
"path": 子图路径,
"left": 子图在原图中的位置,
"top": ...
"width": ...
"height": ...
"result": 子图的检测信息
}
]
}
用于输出给上一级系统。
6.整体流程总结图
AI检测结果(result)
↓
[1] check_error ——日志检查错误
↓
[2] remove_outside_boundingbox ——过滤越界框
↓
[3] remove_approximate_result ——去除重复结果
↓
[4] get_short_image_results_middle ——生成小图 + 上传OBS + 返回JSON
↓
[5] assemble_results ——组装最终返回值

浙公网安备 33010602011771号