You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
500 lines
24 KiB
500 lines
24 KiB
from abc import abstractmethod,ABC
|
|
from shapely.geometry import Point, Polygon
|
|
from myutils.ConfigManager import myCongif
|
|
from myutils.MyLogger_logger import LogHandler
|
|
import numpy as np
|
|
import cv2
|
|
import time
|
|
import queue
|
|
from datetime import datetime, timedelta
|
|
import threading
|
|
import ast
|
|
if myCongif.get_data("model_platform") == "acl":
|
|
import acl
|
|
import platform
|
|
|
|
#-----acl相关------
|
|
SUCCESS = 0 # 成功状态值
|
|
FAILED = 1 # 失败状态值
|
|
ACL_MEM_MALLOC_NORMAL_ONLY = 2 # 申请内存策略, 仅申请普通页
|
|
|
|
class ModelRunData:
|
|
def __init__(self):
|
|
self.channel_id = None
|
|
|
|
|
|
class ModelBase(ABC):
|
|
def __init__(self,path,mMM):
|
|
'''
|
|
模型类实例化
|
|
:param path: 模型文件本身的路径
|
|
:param threshold: 模型的置信阈值
|
|
'''
|
|
self.model_path = path # 模型路径
|
|
self.mMM = mMM # ModelManager
|
|
|
|
self.mylogger = LogHandler().get_logger("ModelManager")
|
|
self.name = None #基于name来查询,用户对模型的配置参数,代表着模型名称需要唯一 2024-6-18 -逻辑还需要完善和验证
|
|
self.version = None
|
|
self.model_type = None # 模型类型 1-图像分类,2-目标检测(yolov5),3-分割模型,4-关键点
|
|
self.system = myCongif.get_data("model_platform") #platform.system() #获取系统平台
|
|
#--2024-7-12调整规则,一个视频通道只允许配置一个算法模型,一个算法模型可以配置给多路通道
|
|
self.channel_list = [] #该模型需要处理的视频通道
|
|
self.cid_copy_list = [] #基于channel_list的备份,提供给遍历线程
|
|
self.channel_data_list =[] #该模型,针对每个通道配置的执行参数 ChannelData --包含了输入和输出数据
|
|
self.cda_copy_list = [] #基于channel_data_list的备份,提供给遍历线程
|
|
self.frame_interval = 1.0 / int(myCongif.get_data("verify_rate"))
|
|
self.init_ok = False
|
|
#启动推理线程 -- 实例化后是否启动工作线程 -- acl这么搞
|
|
self.run = False
|
|
#创建线程锁
|
|
self.list_lock = threading.Lock() #list修改锁
|
|
self.copy_lock = threading.Lock() #副本拷贝锁
|
|
self.read_lock = threading.Lock() #遍历线程锁
|
|
self.readers = 0 #并发读个数
|
|
|
|
|
|
def __del__(self):
|
|
print("资源释放")
|
|
|
|
def addChannel(self,channel_id,channel_data): #这两个参数有点重复,后续考虑优化
|
|
bfind = False
|
|
with self.list_lock:
|
|
for cid in self.channel_list:
|
|
if cid == channel_id:
|
|
bfind = True
|
|
if not bfind:
|
|
self.channel_data_list.append(channel_data)
|
|
self.channel_list.append(channel_id)
|
|
#复制备份
|
|
self.set_copy_list()
|
|
|
|
def delChannel(self,channel_id):#调用删除通道的地方,若channel为空,则停止线程,删除该模型对象
|
|
with self.list_lock:
|
|
for i,cid in enumerate(self.channel_list):
|
|
if cid == channel_id:
|
|
self.channel_list.remove(channel_id)
|
|
#self.channel_data_list[i].cleardata() #释放内存
|
|
self.channel_data_list.pop(i)
|
|
# 复制备份
|
|
self.set_copy_list()
|
|
|
|
def set_copy_list(self): #把list拷贝一个副本
|
|
with self.copy_lock:
|
|
self.cid_copy_list = self.channel_list.copy()
|
|
self.cda_copy_list = self.channel_data_list.copy()
|
|
|
|
# copy使用读写锁,这样能避免三个遍历线程间相互竞争
|
|
def acquire_read(self):
|
|
with self.read_lock:
|
|
self.readers += 1
|
|
if self.readers == 1:
|
|
self.copy_lock.acquire()
|
|
|
|
def release_read(self):
|
|
with self.read_lock:
|
|
self.readers -= 1
|
|
if self.readers == 0:
|
|
self.copy_lock.release()
|
|
|
|
def get_copy_list(self): #线程中拷贝一个本地副本 其实读线程里面执行的时间复杂度也不高,直接用copy锁一样
|
|
self.acquire_read()
|
|
local_id = self.cid_copy_list.copy()
|
|
local_data = self.cda_copy_list.copy()
|
|
self.release_read()
|
|
return local_id,local_data
|
|
|
|
|
|
def strop_th(self):
|
|
'''停止该模型的工作线程'''
|
|
self.run = False
|
|
time.sleep(1) #确认下在哪执行
|
|
#删除list
|
|
del self.channel_list
|
|
|
|
def start_th(self):
|
|
#要确保三个线程对channel_data的读取和修改是线程安全的,或是独立分开的。
|
|
#预处理
|
|
# th_pre = threading.Thread(target=self.th_prework) # 一个视频通道一个线程,线程句柄暂时部保留
|
|
# th_pre.start()
|
|
|
|
#推理
|
|
th_infer = threading.Thread(target=self.th_startwork) # 一个视频通道一个线程,线程句柄暂时部保留
|
|
th_infer.start()
|
|
|
|
#后处理
|
|
# th_post = threading.Thread(target=self.th_postwork) # 一个视频通道一个线程,线程句柄暂时部保留
|
|
# th_post.start()
|
|
|
|
def th_sleep(self,frame_interval,last_time):
|
|
# 控制帧率 -- 推理帧率必须所有模型一致,若模型推理耗时一致,该方案还算可以。
|
|
current_time = time.time()
|
|
elapsed_time = current_time - last_time
|
|
if elapsed_time < frame_interval:
|
|
time.sleep(frame_interval - elapsed_time) # 若小于间隔时间则休眠
|
|
|
|
def is_in_schedule(self,channel_data):
|
|
'''判断当前时间是否在该通道的工作计划时间内'''
|
|
# 验证检测计划,是否在布防时间内
|
|
now = datetime.now() # 获取当前日期和时间
|
|
weekday = now.weekday() # 获取星期几,星期一是0,星期天是6
|
|
hour = now.hour
|
|
if channel_data.schedule[weekday][hour] == 1: # 不在计划则不进行验证,直接返回图片
|
|
return 0
|
|
else:
|
|
next_hour = (now + timedelta(hours=1)).replace(minute=0, second=0, microsecond=0)
|
|
seconds_until_next_hour = (next_hour - now).seconds
|
|
return seconds_until_next_hour
|
|
|
|
def th_prework(self):
|
|
last_pre_time = time.time()
|
|
while self.run:
|
|
#start_do_time = time.time()
|
|
# 控制帧率 -- 推理帧率必须所有模型一致,
|
|
self.th_sleep(self.frame_interval, last_pre_time)
|
|
last_pre_time = time.time()
|
|
#拷贝副本到线程本地
|
|
with self.copy_lock:
|
|
#local_cid = self.cid_copy_list.copy()
|
|
local_cdata = self.cda_copy_list.copy()
|
|
for channel_data in local_cdata: # 如果没有视频通道结束线程
|
|
#判断是否在布防计划内
|
|
sleep_time = self.is_in_schedule(channel_data)
|
|
if sleep_time == 0: #判断是否工作计划内-- 后来判断下是否推理和后处理线程需要休眠
|
|
channel_data.bModel = True
|
|
else:
|
|
channel_data.bModel = False
|
|
time.sleep(sleep_time)
|
|
continue
|
|
# 读取图片进行推理
|
|
ret, img = channel_data.cap.read()
|
|
if not ret:
|
|
continue
|
|
#img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #这个要确认下什么模式输入进预处理 #?
|
|
preimg,scale_ratio, pad_size = self.prework(img) #子类实现-根据每个模型的需要进行预处理
|
|
if not channel_data.preimg_q.full():
|
|
channel_data.preimg_q.put(preimg)
|
|
channel_data.img_q.put(img)
|
|
channel_data.set_in_cale_ratio(scale_ratio, pad_size)
|
|
else:
|
|
self.mylogger.debug("preimg_q--预处理队列满了! infer线程处理过慢!")
|
|
#end_do_time = time.time()
|
|
# # 计算执行时间(秒)
|
|
# execution_time = end_do_time - start_do_time
|
|
# # 输出执行时间
|
|
# print(f"预处理代码执行时间为:{execution_time:.6f} 秒")
|
|
|
|
def th_startwork(self):
|
|
'''模型工作线程 由于有多输入通道,需要将执行任务降低到最少'''
|
|
self._init_acl() #创建context
|
|
if not self._init_resource(): #加载模型文件 -- 正常来说这里不应该失败--上层函数暂时都认为是成功的!需完善
|
|
self.mylogger.error("模型文件初始化加载失败!")
|
|
return
|
|
last_infer_time = time.time()
|
|
#开始工作--- 尽量精简!
|
|
while self.run:
|
|
#start_do_time = time.time()
|
|
# 控制帧率 -- 推理帧率必须所有模型一致,
|
|
self.th_sleep(self.frame_interval, last_infer_time)
|
|
last_infer_time = time.time()
|
|
# 拷贝副本到线程本地
|
|
with self.copy_lock:
|
|
# local_cid = self.cid_copy_list.copy()
|
|
local_cdata = self.cda_copy_list.copy()
|
|
|
|
for channel_data in local_cdata: #如果没有视频通道可以结束线程#?
|
|
if channel_data.preimg_q.empty():
|
|
continue
|
|
img = channel_data.preimg_q.get()
|
|
src_img = channel_data.img_q.get()
|
|
#就执行推理,针对结果的逻辑判断交给后处理线程处理。-- 需要确认除目标识别外其他模型的执行方式
|
|
output = self.execute([img,])[0] # 执行推理
|
|
if len(output) > 0:
|
|
if not channel_data.output_q.full():
|
|
channel_data.output_q.put(output)
|
|
channel_data.infer_img_q.put(src_img) #原图交给后处理线程
|
|
else:
|
|
self.mylogger.debug("output_q--后处理队列满了!后处理过慢!")
|
|
# end_do_time= time.time()
|
|
# # 计算执行时间(秒)
|
|
# execution_time = end_do_time - start_do_time
|
|
# # 输出执行时间
|
|
# print(f"****************推理代码执行时间为:{execution_time:.6f} 秒")
|
|
#结束工作-开始释放资源
|
|
self.release()
|
|
self._del_acl()
|
|
|
|
def th_postwork(self):
|
|
warn_interval = int(myCongif.get_data("warn_interval"))
|
|
last_out_time = time.time()
|
|
while self.run:
|
|
# 控制帧率 -- 推理帧率必须所有模型一致,
|
|
self.th_sleep(self.frame_interval, last_out_time)
|
|
last_out_time = time.time()
|
|
# 拷贝副本到线程本地
|
|
with self.copy_lock:
|
|
local_cid = self.cid_copy_list.copy()
|
|
local_cdata = self.cda_copy_list.copy()
|
|
for i, channel_data in enumerate(local_cdata):
|
|
# 控制帧率 -- 推理帧率必须所有模型一致,若模型推理耗时一致,该方案还算可以。
|
|
if channel_data.output_q.empty():
|
|
continue
|
|
#执行后处理
|
|
output = channel_data.output_q.get()
|
|
img = channel_data.infer_img_q.get()
|
|
cale_ratio = channel_data.cale_ratio
|
|
pad_size = channel_data.pad_size
|
|
|
|
if len(output) <1:
|
|
continue
|
|
filtered_pred_all, bwarn, warn_text = self.postwork(img,output,channel_data.in_check_area,
|
|
channel_data.in_polygon,cale_ratio,pad_size) #子类实现--具体的报警逻辑
|
|
#img的修改应该在原内存空间修改的
|
|
channel_data.result.pop(0) #先把最早的结果推出数组,保障结果数组定长
|
|
if bwarn: # 整个识别有产生报警
|
|
#根据模型设定的时间和占比判断是否
|
|
# 绘制报警文本
|
|
cv2.putText(img, warn_text, (50,50),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
|
|
channel_data.result.append(1)
|
|
else: #没有产生报警也需要记录,统一计算占比
|
|
channel_data.result.append(0)
|
|
|
|
# 处理完的图片后返回-bgr模式 --一头一尾是不是抵消了,可以不做处理#?
|
|
#img_bgr_ndarray = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
|
# 在线程里面完成应该可以减少网页端处理时间
|
|
ret, frame_bgr_webp = cv2.imencode('.jpg', img)
|
|
if not ret:
|
|
buffer_bgr_webp = None
|
|
else:
|
|
buffer_bgr_webp = frame_bgr_webp.tobytes()
|
|
|
|
# 分析图片放入内存中
|
|
#channel_data.add_deque(img_bgr_ndarray) # 缓冲区大小由maxlen控制 超上限后,删除最前的数据
|
|
channel_data.add_deque(img)
|
|
channel_data.increment_counter() # 帧序列加一
|
|
# 一直更新最新帧,提供网页端显示
|
|
channel_data.update_last_frame(buffer_bgr_webp)
|
|
# print(f"{channel_id}--Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
|
|
# 验证result_list -是否触发报警要求 --遍历每个模型执行的result
|
|
|
|
if bwarn:
|
|
result = channel_data.result #最近的检测记录
|
|
proportion = channel_data.proportion #判断报警的占比
|
|
count_one = float(sum(result)) # 1,0 把1累加的和就是1的数量
|
|
ratio_of_ones = count_one / len(result)
|
|
# self.logger.debug(result)
|
|
if ratio_of_ones >= proportion: # 触发报警
|
|
# 基于时间间隔判断
|
|
current_time = time.time()
|
|
elapsed_time = current_time - channel_data.warn_last_time
|
|
if elapsed_time < warn_interval:
|
|
continue
|
|
#处理报警
|
|
channel_data.warn_last_time = current_time
|
|
model_name = channel_data.model_name
|
|
w_s_count = channel_data.warn_save_count #上次保存的缓冲帧序号
|
|
buffer_count = channel_data.get_counter()
|
|
#线程?
|
|
self.mMM.save_warn(model_name, w_s_count, buffer_count, channel_data.copy_deque(),
|
|
channel_data.cap.width, channel_data.cap.height, local_cid[i],
|
|
self.mMM.FPS, self.mMM.fourcc)
|
|
self.mMM.send_warn()
|
|
# 更新帧序列号
|
|
channel_data.warn_save_count = buffer_count
|
|
# 结果记录要清空
|
|
for i in range(len(result)):
|
|
result[i] = 0
|
|
|
|
def draw_polygon(self, img, polygon_points,color=(0, 255, 0)):
|
|
self.polygon = Polygon(ast.literal_eval(polygon_points))
|
|
|
|
points = np.array([self.polygon.exterior.coords], dtype=np.int32)
|
|
cv2.polylines(img, points, isClosed=True, color=color, thickness=2)
|
|
|
|
def is_point_in_region(self, point):
|
|
'''判断点是否在区域内,需要先执行draw_polygon'''
|
|
if self.polygon:
|
|
return self.polygon.contains(Point(point))
|
|
else:
|
|
return False
|
|
|
|
#acl ----- 相关-----
|
|
def _init_acl(self,device_id=0):
|
|
self.context, ret = acl.rt.create_context(device_id) # 显式创建一个Context
|
|
if ret:
|
|
raise RuntimeError(ret)
|
|
print('Init TH-Context Successfully')
|
|
|
|
def _del_acl(self):
|
|
device_id = 0
|
|
# 线程释放context
|
|
ret = acl.rt.destroy_context(self.context) # 释放 Context
|
|
if ret:
|
|
raise RuntimeError(ret)
|
|
print('Deinit TH-Context Successfully')
|
|
|
|
def _init_resource(self):
|
|
''' 初始化模型、输出相关资源。相关数据类型: aclmdlDesc aclDataBuffer aclmdlDataset'''
|
|
print("Init model resource")
|
|
# 加载模型文件
|
|
#self.model_path = "/home/HwHiAiUser/samples/yolo_acl_sample/yolov5s_bs1.om"
|
|
self.model_id, ret = acl.mdl.load_from_file(self.model_path) # 加载模型
|
|
if ret != 0:
|
|
print(f"{self.model_path}---模型加载失败!")
|
|
return False
|
|
self.model_desc = acl.mdl.create_desc() # 初始化模型信息对象
|
|
ret = acl.mdl.get_desc(self.model_desc, self.model_id) # 根据模型ID获取该模型的aclmdlDesc类型数据(描述信息)
|
|
print("[Model] Model init resource stage success")
|
|
# 创建模型输出 dataset 结构
|
|
ret = self._gen_output_dataset() # 创建模型输出dataset结构
|
|
if ret !=0:
|
|
print("[Model] create model output dataset fail")
|
|
return False
|
|
return True
|
|
|
|
def _gen_output_dataset(self):
|
|
''' 组织输出数据的dataset结构 '''
|
|
ret = SUCCESS
|
|
self._output_num = acl.mdl.get_num_outputs(self.model_desc) # 根据aclmdlDesc类型的数据,获取模型的输出个数
|
|
self.output_dataset = acl.mdl.create_dataset() # 创建输出dataset结构
|
|
for i in range(self._output_num):
|
|
temp_buffer_size = acl.mdl.get_output_size_by_index(self.model_desc, i) # 获取模型输出个数
|
|
temp_buffer, ret = acl.rt.malloc(temp_buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY) # 为每个输出申请device内存
|
|
dataset_buffer = acl.create_data_buffer(temp_buffer,
|
|
temp_buffer_size) # 创建输出的data buffer结构,将申请的内存填入data buffer
|
|
_, ret = acl.mdl.add_dataset_buffer(self.output_dataset, dataset_buffer) # 将 data buffer 加入输出dataset(地址)
|
|
|
|
if ret == FAILED:
|
|
self._release_dataset(self.output_dataset) # 失败时释放dataset
|
|
return ret
|
|
print("[Model] create model output dataset success")
|
|
return ret
|
|
|
|
def _gen_input_dataset(self, input_list):
|
|
''' 组织输入数据的dataset结构 '''
|
|
ret = SUCCESS
|
|
self._input_num = acl.mdl.get_num_inputs(self.model_desc) # 获取模型输入个数
|
|
self.input_dataset = acl.mdl.create_dataset() # 创建输入dataset结构
|
|
for i in range(self._input_num):
|
|
item = input_list[i] # 获取第 i 个输入数据
|
|
data_ptr = acl.util.bytes_to_ptr(item.tobytes()) # 获取输入数据字节流
|
|
size = item.size * item.itemsize # 获取输入数据字节数
|
|
dataset_buffer = acl.create_data_buffer(data_ptr, size) # 创建输入dataset buffer结构, 填入输入数据
|
|
_, ret = acl.mdl.add_dataset_buffer(self.input_dataset, dataset_buffer) # 将dataset buffer加入dataset
|
|
|
|
if ret == FAILED:
|
|
self._release_dataset(self.input_dataset) # 失败时释放dataset
|
|
#print("[Model] create model input dataset success")
|
|
|
|
def _unpack_bytes_array(self, byte_array, shape, datatype):
|
|
''' 将内存不同类型的数据解码为numpy数组 '''
|
|
np_type = None
|
|
|
|
# 获取输出数据类型对应的numpy数组类型和解码标记
|
|
if datatype == 0: # ACL_FLOAT
|
|
np_type = np.float32
|
|
elif datatype == 1: # ACL_FLOAT16
|
|
np_type = np.float16
|
|
elif datatype == 3: # ACL_INT32
|
|
np_type = np.int32
|
|
elif datatype == 8: # ACL_UINT32
|
|
np_type = np.uint32
|
|
else:
|
|
print("unsurpport datatype ", datatype)
|
|
return
|
|
|
|
# 将解码后的数据组织为numpy数组,并设置shape和类型
|
|
return np.frombuffer(byte_array, dtype=np_type).reshape(shape)
|
|
|
|
def _output_dataset_to_numpy(self):
|
|
''' 将模型输出解码为numpy数组 '''
|
|
dataset = []
|
|
# 遍历每个输出
|
|
for i in range(self._output_num):
|
|
buffer = acl.mdl.get_dataset_buffer(self.output_dataset, i) # 从输出dataset中获取buffer
|
|
data_ptr = acl.get_data_buffer_addr(buffer) # 获取输出数据内存地址
|
|
size = acl.get_data_buffer_size(buffer) # 获取输出数据字节数
|
|
narray = acl.util.ptr_to_bytes(data_ptr, size) # 将指针转为字节流数据
|
|
|
|
# 根据模型输出的shape和数据类型,将内存数据解码为numpy数组
|
|
outret = acl.mdl.get_output_dims(self.model_desc, i)[0]
|
|
dims = outret["dims"] # 获取每个输出的维度
|
|
# print(f"name:{outret['name']}")
|
|
# print(f"dimCount:{outret['dimCount']}")
|
|
'''
|
|
dims = {
|
|
"name": xxx, #tensor name
|
|
"dimCount":xxx,#shape中的维度个数
|
|
"dims": [xx, xx, xx] # 维度信息 --- 取的这个
|
|
}
|
|
'''
|
|
datatype = acl.mdl.get_output_data_type(self.model_desc, i) # 获取每个输出的数据类型 --就数据类型float16,int8等
|
|
output_nparray = self._unpack_bytes_array(narray, tuple(dims), datatype) # 解码为numpy数组
|
|
dataset.append(output_nparray)
|
|
return dataset
|
|
|
|
def execute(self, input_list):
|
|
'''创建输入dataset对象, 推理完成后, 将输出数据转换为numpy格式'''
|
|
self._gen_input_dataset(input_list) # 创建模型输入dataset结构
|
|
ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset) # 调用离线模型的execute推理数据
|
|
if ret:
|
|
self.mylogger.error(f"acl.mdl.execute fail!--{ret}")
|
|
self._release_dataset(self.input_dataset) # 失败时释放dataset --创建输入空间失败时会释放。
|
|
return None
|
|
out_numpy = self._output_dataset_to_numpy() # 将推理输出的二进制数据流解码为numpy数组, 数组的shape和类型与模型输出规格一致
|
|
self._release_dataset(self.input_dataset) # 释放dataset -- 要不要执行需要验证
|
|
return out_numpy
|
|
|
|
def release(self):
|
|
''' 释放模型相关资源 '''
|
|
if self._is_released:
|
|
return
|
|
|
|
print("Model start release...")
|
|
self._release_dataset(self.input_dataset) # 释放输入数据结构
|
|
self.input_dataset = None # 将输入数据置空
|
|
self._release_dataset(self.output_dataset) # 释放输出数据结构
|
|
self.output_dataset = None # 将输出数据置空
|
|
|
|
if self.model_id:
|
|
ret = acl.mdl.unload(self.model_id) # 卸载模型
|
|
if self.model_desc:
|
|
ret = acl.mdl.destroy_desc(self.model_desc) # 释放模型描述信息
|
|
self._is_released = True
|
|
print("Model release source success")
|
|
#测试使用
|
|
#self._del_acl()
|
|
|
|
def _release_dataset(self, dataset):
|
|
''' 释放 aclmdlDataset 类型数据 '''
|
|
if not dataset:
|
|
return
|
|
num = acl.mdl.get_dataset_num_buffers(dataset) # 获取数据集包含的buffer个数
|
|
for i in range(num):
|
|
data_buf = acl.mdl.get_dataset_buffer(dataset, i) # 获取buffer指针
|
|
if data_buf:
|
|
ret = acl.destroy_data_buffer(data_buf) # 释放buffer
|
|
ret = acl.mdl.destroy_dataset(dataset) # 销毁数据集
|
|
|
|
@abstractmethod
|
|
def prework(self, image): # 预处理
|
|
pass
|
|
|
|
@abstractmethod
|
|
def verify(self,image,data,isdraw=1): #
|
|
'''
|
|
:param image: 需要验证的图片
|
|
:param data: select t1.model_id,t1.check_area,t1.polygon ,t2.duration_time,t2.proportion,t2.model_path
|
|
:param isdraw: 是否需要绘制线框:0-不绘制,1-绘制
|
|
:return: detections,bwarn,warntext bwarn:0-没有识别到符合要求的目标,1-没有识别到符合要求的目标。
|
|
'''
|
|
pass
|
|
|
|
@abstractmethod
|
|
def postwork(self,image,output,check_area,polygon,scale_ratio, pad_size): # 后处理
|
|
pass
|
|
|
|
|
|
|
|
if __name__ =="__main__":
|
|
pass
|