pd9427
This commit is contained in:
BIN
vision/charge_3cls/charge0324.rknn
Normal file
BIN
vision/charge_3cls/charge0324.rknn
Normal file
Binary file not shown.
131
vision/charge_3cls/charge_cls_rknn.py
Normal file
131
vision/charge_3cls/charge_cls_rknn.py
Normal file
@ -0,0 +1,131 @@
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from collections import deque
|
||||
|
||||
class StableClassJudge:
|
||||
"""
|
||||
连续三帧稳定判决器:
|
||||
- class0 / class1 连续 3 帧 -> 输出
|
||||
- class2 -> 清空计数,重新统计
|
||||
"""
|
||||
|
||||
def __init__(self, stable_frames=3, ignore_class=2):
|
||||
self.stable_frames = stable_frames
|
||||
self.ignore_class = ignore_class
|
||||
self.buffer = deque(maxlen=stable_frames)
|
||||
|
||||
def reset(self):
|
||||
self.buffer.clear()
|
||||
|
||||
def update(self, class_id):
|
||||
if class_id == self.ignore_class:
|
||||
self.reset()
|
||||
return None
|
||||
|
||||
self.buffer.append(class_id)
|
||||
|
||||
if len(self.buffer) < self.stable_frames:
|
||||
return None
|
||||
|
||||
if len(set(self.buffer)) == 1:
|
||||
stable_class = self.buffer[0]
|
||||
self.reset()
|
||||
return stable_class
|
||||
|
||||
return None
|
||||
|
||||
# ---------------------------
|
||||
# 三分类映射
|
||||
# ---------------------------
|
||||
CLASS_NAMES = {
|
||||
0: "插好",
|
||||
1: "未插好",
|
||||
2: "有遮挡"
|
||||
}
|
||||
|
||||
# ---------------------------
|
||||
# RKNN 全局实例(只加载一次)
|
||||
# ---------------------------
|
||||
_global_rknn = None
|
||||
|
||||
def init_rknn_model(model_path):
|
||||
from rknnlite.api import RKNNLite
|
||||
global _global_rknn
|
||||
if _global_rknn is not None:
|
||||
return _global_rknn
|
||||
|
||||
rknn = RKNNLite(verbose=False)
|
||||
ret = rknn.load_rknn(model_path)
|
||||
if ret != 0:
|
||||
raise RuntimeError(f"Load RKNN failed: {ret}")
|
||||
|
||||
ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
|
||||
if ret != 0:
|
||||
raise RuntimeError(f"Init runtime failed: {ret}")
|
||||
|
||||
_global_rknn = rknn
|
||||
print(f"[INFO] RKNN 模型加载成功:{model_path}")
|
||||
return rknn
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# 预处理(输入 uint8,RKNN 内部转 float32)
|
||||
# ---------------------------
|
||||
def resize_stretch(image, size=640):
|
||||
return cv2.resize(image, (size, size))
|
||||
|
||||
def preprocess_image_for_rknn(img, size=640):
|
||||
# 输入必须是 uint8 [0,255],即使模型是 float32
|
||||
img_resized = resize_stretch(img, size=size)
|
||||
img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
|
||||
input_tensor = np.expand_dims(img_rgb, axis=0).astype(np.uint8) # NHWC, uint8
|
||||
return np.ascontiguousarray(input_tensor)
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# 单次 RKNN 推理(三分类,float32 模型)
|
||||
# ---------------------------
|
||||
def rknn_classify_preprocessed(input_tensor, model_path):
|
||||
rknn = init_rknn_model(model_path)
|
||||
outs = rknn.inference([input_tensor])
|
||||
|
||||
# 直接得到 logits
|
||||
probs = outs[0].flatten().astype(np.float32) # shape: (3,)
|
||||
class_id = int(np.argmax(probs))
|
||||
return class_id, probs
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# 单张图片推理
|
||||
# ---------------------------
|
||||
def classify_single_image(frame, model_path, size=640):
|
||||
if frame is None:
|
||||
raise FileNotFoundError("输入帧为空")
|
||||
|
||||
input_tensor = preprocess_image_for_rknn(frame, size=size)
|
||||
class_id, probs = rknn_classify_preprocessed(input_tensor, model_path)
|
||||
class_name = CLASS_NAMES.get(class_id, f"未知类别 ({class_id})")
|
||||
|
||||
return {
|
||||
"class_id": class_id,
|
||||
"class": class_name,
|
||||
"score": round(float(probs[class_id]), 4),
|
||||
"raw": probs.tolist()
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# 示例调用
|
||||
# ---------------------------
|
||||
if __name__ == "__main__":
|
||||
model_path = "charge0324.rknn"
|
||||
image_path = "class2.jpg"
|
||||
|
||||
frame = cv2.imread(image_path)
|
||||
if frame is None:
|
||||
raise FileNotFoundError(f"无法读取图片:{image_path}")
|
||||
|
||||
result = classify_single_image(frame, model_path)
|
||||
print("[RESULT]", result)
|
||||
88
vision/charge_3cls/charge_utils.py
Normal file
88
vision/charge_3cls/charge_utils.py
Normal file
@ -0,0 +1,88 @@
|
||||
import os
|
||||
import cv2
|
||||
#from rknnlite.api import RKNNLite
|
||||
import time
|
||||
|
||||
# classify_single_image, StableClassJudge, CLASS_NAMES 已在 muju_cls_rknn 中定义
|
||||
from .charge_cls_rknn import classify_single_image, StableClassJudge, CLASS_NAMES
|
||||
|
||||
# 获取当前文件所在目录的绝对路径
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
def run_stable_charge_loop():
|
||||
"""
|
||||
image_source: cv2.VideoCapture 对象
|
||||
"""
|
||||
_ret=None
|
||||
# 使用相对于当前文件的绝对路径
|
||||
model_path = os.path.join(current_dir, "charge0324.rknn")
|
||||
# roi_file = os.path.join(current_dir, "roi_coordinates", "muju_roi.txt")
|
||||
RTSP_URL = "rtsp://admin:XJ123456@192.168.250.60:554/streaming/channels/101"
|
||||
stable_frames=5
|
||||
print(f"正在连接 RTSP 流: {RTSP_URL}")
|
||||
cap =None
|
||||
try:
|
||||
cap = cv2.VideoCapture(RTSP_URL)
|
||||
# 降低 RTSP 延迟(部分摄像头支持)
|
||||
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
||||
|
||||
if not cap.isOpened():
|
||||
print("无法打开 RTSP 流,请检查网络、账号密码或 URL")
|
||||
return None
|
||||
|
||||
print("RTSP 流连接成功,开始推理...")
|
||||
|
||||
judge = StableClassJudge(
|
||||
stable_frames=stable_frames,
|
||||
ignore_class=2 # 忽略“有遮挡”类别参与稳定判断
|
||||
)
|
||||
|
||||
if not hasattr(cap, "read"):
|
||||
raise TypeError("image_source 必须是 cv2.VideoCapture 实例")
|
||||
_max_count=10
|
||||
while True:
|
||||
_max_count=_max_count-1
|
||||
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
print("无法读取视频帧(可能是流断开或结束)")
|
||||
continue
|
||||
# 上下左右翻转
|
||||
# frame = cv2.flip(frame, -1)
|
||||
|
||||
# ---------------------------
|
||||
# 单帧推理
|
||||
# ---------------------------
|
||||
result = classify_single_image(frame, model_path)
|
||||
|
||||
|
||||
class_id = result["class_id"]
|
||||
class_name = result["class"]
|
||||
score = result["score"]
|
||||
|
||||
print(f"[FRAME] {class_name} | conf={score:.3f}")
|
||||
if score>0.8:
|
||||
# ---------------------------
|
||||
# 稳定判断
|
||||
# ---------------------------
|
||||
stable_class_id = judge.update(class_id)
|
||||
|
||||
if stable_class_id is not None:
|
||||
_ret=CLASS_NAMES[stable_class_id]
|
||||
if _ret is None:
|
||||
print("-------当前振捣棒检测为空,继续等待稳定------")
|
||||
continue
|
||||
if _ret=="插好":
|
||||
break
|
||||
print(f"-------当前振捣棒检测为:{_ret},继续等待稳定------")
|
||||
else:
|
||||
print("-------当前振捣棒检测为空,继续等待稳定------")
|
||||
|
||||
time.sleep(0.1)
|
||||
finally:
|
||||
if cap is not None:
|
||||
cap.release()
|
||||
return _ret
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
vision/charge_3cls/class1.png
Normal file
BIN
vision/charge_3cls/class1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
BIN
vision/charge_3cls/class2.png
Normal file
BIN
vision/charge_3cls/class2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
@ -17,7 +17,8 @@ from vision.camera_picture import save_camera_picture
|
||||
from busisness.blls import ArtifactBll,PDRecordBll
|
||||
from busisness.models import ArtifactInfoModel,PDRecordModel
|
||||
from service.mould_service import app_web_service
|
||||
from core.system_state import SystemState,FeedStatus,Upper_Door_Position
|
||||
from core.system_state import SystemState,FeedStatus,Upper_Door_Position,PD_StatusEnum
|
||||
from core.core_utils import CoreUtils
|
||||
from dataclasses import asdict
|
||||
import json
|
||||
import math
|
||||
@ -301,6 +302,9 @@ class VisualCallback:
|
||||
def _arch_loop(self):
|
||||
"""破拱线程"""
|
||||
while self._is_running:
|
||||
if self._is_finish:
|
||||
time.sleep(2)
|
||||
continue
|
||||
try:
|
||||
current_time = time.time()
|
||||
# 检查下料斗破拱(只有在下料过程中才检查)
|
||||
@ -314,8 +318,9 @@ class VisualCallback:
|
||||
if (_weight_changed< 200) and \
|
||||
(current_time - self._last_arch_time) >= 2:
|
||||
self._last_arch_time = current_time
|
||||
print('---------------------第一阶段振动5秒(小于200KG)-----------------')
|
||||
self.relay_feed.control_arch_lower_open_sync(5)
|
||||
print('---------------------第一阶段振动3秒(小于200KG)-----------------')
|
||||
if self._current_angle>25:
|
||||
self.relay_feed.control_arch_lower_open_sync(3)
|
||||
self._last_arch_one_weight = _arch_weight
|
||||
continue
|
||||
self._last_arch_one_weight = _arch_weight
|
||||
@ -345,8 +350,9 @@ class VisualCallback:
|
||||
if (_weight_changed < 100) and \
|
||||
(current_time - self._last_arch_time) >= 2:
|
||||
self._last_arch_time = current_time
|
||||
print('---------------------第三阶段振动5秒(小于100KG)-----------------')
|
||||
self.relay_feed.control_arch_lower_open_sync(5)
|
||||
print('---------------------第三阶段振动3秒(小于100KG)-----------------')
|
||||
if self._current_angle>25:
|
||||
self.relay_feed.control_arch_lower_open_sync(3)
|
||||
self._last_arch_three_weight = _arch_weight
|
||||
continue
|
||||
self._last_arch_three_weight = _arch_weight
|
||||
@ -379,7 +385,8 @@ class VisualCallback:
|
||||
(current_time - self._last_arch_time) >= 2:
|
||||
self._last_arch_time = current_time
|
||||
print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg))-----------------')
|
||||
self.relay_feed.control_arch_lower_open_sync(3)
|
||||
if self._current_angle>25:
|
||||
self.relay_feed.control_arch_lower_open_sync(3)
|
||||
self._last_arch_five_weight = _arch_weight
|
||||
continue
|
||||
self._last_arch_five_weight = _arch_weight
|
||||
@ -475,7 +482,7 @@ class VisualCallback:
|
||||
|
||||
def _run_feed(self):
|
||||
_is_api_request=True
|
||||
while True:
|
||||
while self.state.running:
|
||||
# print("------------已启动----------------")
|
||||
if self._is_feed_start:
|
||||
|
||||
@ -490,15 +497,23 @@ class VisualCallback:
|
||||
_is_aligned=self._aligned_get_times(1)
|
||||
if _is_aligned:
|
||||
_is_api_request=True
|
||||
if self.state.pd_status==PD_StatusEnum.PD_Ready:
|
||||
self.state.pd_status=PD_StatusEnum.PD_TimeOut
|
||||
print('------------启动程序后,进入第一块-------------')
|
||||
self._is_first_module=False
|
||||
self._mould_before_aligned=True
|
||||
if self._cur_mould_model:
|
||||
self.state._db_mould_status={
|
||||
self.state.current_mould=self._cur_mould_model
|
||||
self.state.current_block_number=CoreUtils.get_number_by_mould_code(self._cur_mould_model.MouldCode)
|
||||
self.state._db_mould_status={
|
||||
'mould_code':self._cur_mould_model.MouldCode,
|
||||
'status':2,
|
||||
'weight':0,
|
||||
}
|
||||
else:
|
||||
self.state.current_mould=None
|
||||
self.state.current_block_number=''
|
||||
|
||||
|
||||
_current_weight=self.transmitter_controller.read_data(2)
|
||||
if _current_weight:
|
||||
@ -508,6 +523,8 @@ class VisualCallback:
|
||||
return
|
||||
|
||||
self.state._feed_status=FeedStatus.FCheckGB
|
||||
# if not self.state.current_block_number=='F':
|
||||
# self.state.pd_status=PD_StatusEnum.PD_Ready
|
||||
# self.is_start_visual=True
|
||||
self.run_feed_all()
|
||||
elif self._is_finish and self._is_finish_ratio>=0.7:
|
||||
@ -519,6 +536,8 @@ class VisualCallback:
|
||||
if _is_not_aligned:
|
||||
#标志位
|
||||
self._mould_before_aligned=False
|
||||
print('------------连续盖板未对齐完成,进入派单标志-------------')
|
||||
self.state.pd_status=PD_StatusEnum.PD_Ready
|
||||
#print('------------连续盖板未对齐-------------')
|
||||
else:
|
||||
if _is_api_request:
|
||||
@ -529,6 +548,19 @@ class VisualCallback:
|
||||
print('------------进入连续生产-------------')
|
||||
self._mould_before_aligned=True
|
||||
_is_api_request=True
|
||||
if self.state.pd_status==PD_StatusEnum.PD_Ready:
|
||||
self.state.pd_status=PD_StatusEnum.PD_TimeOut
|
||||
if self._cur_mould_model:
|
||||
self.state.current_mould=self._cur_mould_model
|
||||
self.state.current_block_number=CoreUtils.get_number_by_mould_code(self._cur_mould_model.MouldCode)
|
||||
self.state._db_mould_status={
|
||||
'mould_code':self._cur_mould_model.MouldCode,
|
||||
'status':2,
|
||||
'weight':0,
|
||||
}
|
||||
else:
|
||||
self.state.current_mould=None
|
||||
self.state.current_block_number=''
|
||||
|
||||
_current_weight=self.transmitter_controller.read_data(2)
|
||||
if not _current_weight:
|
||||
@ -545,6 +577,9 @@ class VisualCallback:
|
||||
self.init_val()
|
||||
self._init_lower_weight=_current_weight
|
||||
self.state._feed_status=FeedStatus.FCheckGB
|
||||
# if not self.state.current_block_number=='F':
|
||||
#这里需要判断状态是否发送完成,如果没发送完成,应该报警(时间太长),
|
||||
# self.state.pd_status=PD_StatusEnum.PD_Ready
|
||||
self.run_feed_all()
|
||||
|
||||
# else:
|
||||
@ -665,9 +700,9 @@ class VisualCallback:
|
||||
self._mould_need_weight=0.54*2416
|
||||
need_total_weight=self._mould_need_weight
|
||||
if initial_lower_weight>100:
|
||||
self.state._feed_status=FeedStatus.FFeed5
|
||||
self.state.vf_status=2
|
||||
if not self._is_finish:
|
||||
self.state._feed_status=FeedStatus.FFeed5
|
||||
self.state.vf_status=2
|
||||
self.is_start_visual=True
|
||||
self._is_feed_stage=5
|
||||
while not self._is_finish:
|
||||
@ -716,11 +751,13 @@ class VisualCallback:
|
||||
first_finish_weight=0
|
||||
self._mould_need_weight=1.91*2416
|
||||
need_total_weight=self._mould_need_weight
|
||||
if self._is_finish:
|
||||
return
|
||||
# start_time=None
|
||||
self.is_start_visual=True
|
||||
self.state._feed_status=FeedStatus.FFeed1
|
||||
self.state.vf_status=1
|
||||
if initial_lower_weight>100:
|
||||
self.is_start_visual=True
|
||||
#下料斗的料全部下完
|
||||
self._is_feed_stage=1
|
||||
while not self._is_finish:
|
||||
@ -745,6 +782,8 @@ class VisualCallback:
|
||||
self.close_lower_door_visual()
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
self.is_start_visual=False
|
||||
_current_lower_weight=loc_mitter.read_data(2)
|
||||
if _current_lower_weight is None:
|
||||
print("-------下料斗重量异常---------")
|
||||
@ -759,7 +798,8 @@ class VisualCallback:
|
||||
#print('------------上料斗未就位----------------')
|
||||
# print('------------上料斗未就位----------------')
|
||||
time.sleep(1)
|
||||
|
||||
if self._is_finish:
|
||||
return
|
||||
if self.plc_data in self.plc_valid_data:
|
||||
print(f'------------上料斗就位(上料斗往下料斗阶段)-------------')
|
||||
#打开上料斗出砼门,开5就,开三分之一下
|
||||
@ -801,6 +841,8 @@ class VisualCallback:
|
||||
else:
|
||||
loc_relay.control_upper_close_sync(6+loc_time_count)
|
||||
self.state._upper_door_closed=0
|
||||
if self._is_finish:
|
||||
return
|
||||
self.is_start_visual=True
|
||||
initial_lower_weight=loc_mitter.read_data(2)
|
||||
if initial_lower_weight is None:
|
||||
@ -828,14 +870,17 @@ class VisualCallback:
|
||||
break
|
||||
# print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------')
|
||||
time.sleep(0.5)
|
||||
self.is_start_visual=False
|
||||
_current_lower_weight=loc_mitter.read_data(2)
|
||||
if _current_lower_weight is None:
|
||||
print("-------下料斗重量异常(第二次下到模)---------")
|
||||
return
|
||||
first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight
|
||||
|
||||
print(f'------------已下料(第二次): {first_finish_weight}kg-------------')
|
||||
print(f'------------已下料(第二次): {first_finish_weight}kg-------------')
|
||||
|
||||
if self._is_finish:
|
||||
return
|
||||
self._is_feed_stage=0
|
||||
if self.plc_data in self.plc_valid_data:
|
||||
self.state._upper_door_closed=2
|
||||
@ -1147,7 +1192,7 @@ class VisualCallback:
|
||||
if self._is_feed_stage==1 or self._is_feed_stage==3:
|
||||
#根据溢料状态动态调整目标角度
|
||||
if overflow_detected == "大堆料":
|
||||
if not self.state.mould_vibrate_status:
|
||||
if not self.state._mould_vibrate_status:
|
||||
TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小
|
||||
else:
|
||||
TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右
|
||||
@ -1379,11 +1424,13 @@ class VisualCallback:
|
||||
# print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}")
|
||||
self.plc_data=data
|
||||
if self.plc_data in self.plc_valid_data:
|
||||
if self.state._upper_door_position!=Upper_Door_Position.ZDS:
|
||||
if self.state._upper_door_position!=Upper_Door_Position.ZDS:
|
||||
self.state._upper_door_position=Upper_Door_Position.ZDS
|
||||
self.relay_controller.control(self.relay_controller.UPPER_TO_ZD, 'close')
|
||||
elif self.plc_data in self.plc_valid_data_jbl:
|
||||
if self.state._upper_door_position!=Upper_Door_Position.JBL:
|
||||
self.state._upper_door_position=Upper_Door_Position.JBL
|
||||
self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'close')
|
||||
else:
|
||||
if self.state._upper_door_position!=Upper_Door_Position.Returning:
|
||||
self.state._upper_door_position=Upper_Door_Position.Returning
|
||||
@ -1415,14 +1462,20 @@ class VisualCallback:
|
||||
|
||||
def get_current_mould(self):
|
||||
"""获取当前要浇筑的管片"""
|
||||
# if self.state.pd_status==PD_StatusEnum.PD_Ready:
|
||||
# self.state.pd_status=PD_StatusEnum.PD_TimeOut
|
||||
_not_poured=app_web_service.get_not_pour_artifacts()
|
||||
if _not_poured is not None and len(_not_poured)>=1:
|
||||
_cur_poured_model=_not_poured[-1]
|
||||
if _cur_poured_model.MouldCode:
|
||||
self._cur_mould_model=_cur_poured_model
|
||||
self.state.current_mould=_cur_poured_model
|
||||
# self.state.current_mould=_cur_poured_model
|
||||
# self.state.current_block_number=CoreUtils.get_number_by_mould_code(_cur_poured_model.MouldCode)
|
||||
print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}')
|
||||
else:
|
||||
self._cur_mould_model=None
|
||||
# self.state.current_mould=None
|
||||
# self.state.current_block_number=''
|
||||
print('当前没有未浇筑的管片')
|
||||
|
||||
def __del__(self):
|
||||
|
||||
@ -741,6 +741,8 @@ class VisualCallback:
|
||||
self.close_lower_door_visual()
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
self.is_start_visual=False
|
||||
_current_lower_weight=loc_mitter.read_data(2)
|
||||
if _current_lower_weight is None:
|
||||
print("-------下料斗重量异常---------")
|
||||
@ -1131,7 +1133,7 @@ class VisualCallback:
|
||||
if self._is_feed_stage==1 or self._is_feed_stage==3:
|
||||
#根据溢料状态动态调整目标角度
|
||||
if overflow_detected == "大堆料":
|
||||
if not self.state.mould_vibrate_status:
|
||||
if not self.state._mould_vibrate_status:
|
||||
TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小
|
||||
else:
|
||||
TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右
|
||||
|
||||
1470
vision/visual_callback_dq_12.py
Normal file
1470
vision/visual_callback_dq_12.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user