BW:Debug Loop

   

   

   

   

我写了一段脚本,请你根据我提供的代码写出详细逻辑和实现的功能:#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ """ import os, sys, json, time, cv2, numpy as np, serial, threading, gc from datetime import datetime from collections import deque import logging import configparser # ----------- 日志 ----------- log_file = os.path.join(os.path.dirname(sys.executable), 'run.log') \ if getattr(sys, 'frozen', False) else 'run.log' logging.basicConfig( level=logging.INFO, format='%(asctime)s.%(msecs)03d | %(levelname)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S', handlers=[logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler()] ) # ----------- 配置 ----------- CONFIG_NAME = 'config.ini' def load_config(): cfg = configparser.ConfigParser() if not os.path.exists(CONFIG_NAME): cfg['rtsp'] = {'url': 'rtsp://admin:tp123456@192.168.0.46:554/ch1/main/av_stream'} cfg['serial'] = {'com': 'COM1', 'baud': '115200'} with open(CONFIG_NAME, 'w', encoding='utf-8') as f: cfg.write(f) logging.info("已生成默认配置文件 %s,下次启动可修改", CONFIG_NAME) cfg.read(CONFIG_NAME, encoding='utf-8') return cfg CFG = load_config() RTSP_URL = CFG.get('rtsp', 'url') SERIAL_COM = CFG.get('serial', 'com') SERIAL_BAUD= CFG.getint('serial', 'baud') # ----------- illumin 线程 ----------- class IlluminReader: def __init__(self, com, baud, buf_dur=5): self.com = com self.baud = baud self.buf_dur= buf_dur self.ser = None self.buf = deque() # (ts, value) self.lock = threading.Lock() self.running= True self.thread = None # 对外 def start(self): try: self.ser = serial.Serial(self.com, self.baud, timeout=1) cmd = "while :;do ubus call image get_illumin;sleep 1;done &\n" self.ser.write(cmd.encode()) logging.info("串口 %s 已打开并发送循环命令", self.com) except Exception as e: logging.error("串口打开失败: %s", e) return False self.thread = threading.Thread(target=self._loop, daemon=True) self.thread.start() return True def stop(self): self.running = False if self.thread: self.thread.join(2) if self.ser and self.ser.is_open: self.ser.close() def get(self, target_time): with self.lock: if not self.buf: return None, None, None best = min(self.buf, key=lambda x: abs(x[0] - target_time)) return best[1], best[0], abs(best[0] - target_time) # 内部 def _loop(self): while self.running: try: line = self.ser.readline().decode('utf-8', errors='ignore').strip() if not line: continue val = self._parse(line) if val is None: continue now = time.time() with self.lock: self.buf.append((now, val)) while self.buf and now - self.buf[0][0] > self.buf_dur: self.buf.popleft() except Exception as e: logging.error("illumin 读线程异常: %s", e) time.sleep(1) @staticmethod def _parse(line): try: if line.startswith('{') and line.endswith('}'): return float(json.loads(line)['illumin']) import re nums = re.findall(r'\d+\.?\d*', line) return float(nums[0]) if nums else None except Exception: return None # ----------- 检测器 ----------- class ColorTransitionDetector: def __init__(self): self.cap = None self.frame_buffer = deque() self.timestamp_buffer= deque() self.buffer_size = 0 self.current_state = None self.color_counter = 0 self.bw_counter = 0 self.transition_log = [] self.COLOR_THRESHOLD = 15 self.BW_THRESHOLD = 5 self.CONSEC_FRAMES = 3 self.PRE_TIME = 3 self.POST_TIME = 5 self.INITIAL_FRAMES = 5 self.init_sats = [] self.state_ok = False self.frame_count = 0 self.last_frame_time= time.time() self.frame_interval = 0.1 self.illumin = IlluminReader(SERIAL_COM, SERIAL_BAUD) self.pending = [] self._chk_dirs() def _chk_dirs(self): self.save_dir = os.path.join(os.path.dirname(sys.executable), 'screens') \ if getattr(sys, 'frozen', False) else 'screens' os.makedirs(self.save_dir, exist_ok=True) def init_cam(self): logging.info("连接摄像头...") self.cap = cv2.VideoCapture(RTSP_URL) if not self.cap.isOpened(): logging.error("摄像头打开失败") return False self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) fps = max(self.cap.get(cv2.CAP_PROP_FPS), 8) self.buffer_size = int((self.PRE_TIME + self.POST_TIME) * fps) self.frame_buffer = deque(maxlen=self.buffer_size) self.timestamp_buffer = deque(maxlen=self.buffer_size) self.frame_interval = 1.0 / fps logging.info("缓冲区帧数: %d", self.buffer_size) return True def reconnect(self): if self.cap: self.cap.release() time.sleep(2) return self.init_cam() def calc_sat(self, frame): try: small = cv2.resize(frame, (320, 240)) hsv = cv2.cvtColor(small, cv2.COLOR_BGR2HSV) return np.mean(hsv[:, :, 1]) except Exception as e: logging.error("饱和度计算失败: %s", e) return 0 def detect(self, sat): if not self.state_ok: return False, "" detected = False ttype = "" if self.current_state == "bw": if sat > self.COLOR_THRESHOLD: self.color_counter += 1; self.bw_counter = 0 if self.color_counter >= self.CONSEC_FRAMES: ttype, detected, self.current_state = "btc", True, "color" self.color_counter = 0 else: self.color_counter = 0 else: if sat < self.BW_THRESHOLD: self.bw_counter += 1; self.color_counter = 0 if self.bw_counter >= self.CONSEC_FRAMES: ttype, detected, self.current_state = "ctb", True, "bw" self.bw_counter = 0 else: self.bw_counter = 0 return detected, ttype def proc_frame(self, frame): self.frame_count += 1 now = time.time() elapsed = now - self.last_frame_time if elapsed < self.frame_interval: time.sleep(self.frame_interval - elapsed) self.last_frame_time = now small = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5) self.frame_buffer.append(small.copy()) self.timestamp_buffer.append(now) sat = self.calc_sat(small) if not self.state_ok: self.init_sats.append(sat) if len(self.init_sats) >= self.INITIAL_FRAMES: avg = np.mean(self.init_sats) self.current_state = "color" if avg > self.COLOR_THRESHOLD else "bw" self.state_ok = True logging.info("初始状态: %s (avg=%.2f)", self.current_state, avg) detected, ttype = self.detect(sat) if detected: ts_str = datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3] self.transition_log.append(f"{ts_str} {ttype}") logging.info("检测到 %s 饱和度=%.2f", ttype.upper(), sat) # 3 s 前 illumin ill, ill_ts, diff = self.illumin.get(now - 3) if ill is not None: logging.info("转换前3秒 illumin=%.2f (时间差=%.2fs)", ill, diff) else: logging.warning("未找到转换前3秒 illumin 记录") self.save_pre(now, ts_str, ttype) self.pending.append((now + self.POST_TIME, ts_str, ttype)) return sat, detected, small def save_pre(self, trans_time, ts_str, ttype): if not self.frame_buffer: return target = trans_time - self.PRE_TIME idx = 0 for i, t in enumerate(self.timestamp_buffer): if t >= target: idx = i; break frame = self.frame_buffer[idx] h = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) w = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) full = cv2.resize(frame, (w, h), interpolation=cv2.INTER_CUBIC) path = os.path.join(self.save_dir, f"{ts_str}_{ttype}_pre{self.PRE_TIME}s.jpg") if cv2.imwrite(path, full, [cv2.IMWRITE_JPEG_QUALITY, 90]): logging.info("已保存前截图: %s", os.path.basename(path)) def save_post(self, frame, ts_str, ttype): h = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) w = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) full = cv2.resize(frame, (w, h), interpolation=cv2.INTER_CUBIC) path = os.path.join(self.save_dir, f"{ts_str}_{ttype}_post{self.POST_TIME}s.jpg") if cv2.imwrite(path, full, [cv2.IMWRITE_JPEG_QUALITY, 90]): logging.info("已保存后截图: %s", os.path.basename(path)) def run(self): if not self.init_cam(): return if not self.illumin.start(): logging.warning("illumin 不可用") try: while True: ret, frame = self.cap.read() if not ret: if not self.reconnect(): time.sleep(5) continue sat, detected, small = self.proc_frame(frame) # 检查后截图 now = time.time() for t in list(self.pending): if now >= t[0]: self.save_post(frame, t[1], t[2]) self.pending.remove(t) # 可视化 display = small.copy() color = (0, 0, 255) if detected else (0, 255, 0) cv2.putText(display, f"sat={sat:.1f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) cv2.imshow("ColorTransitionDetector", display) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: self.illumin.stop() if self.cap: self.cap.release() cv2.destroyAllWindows() # ----------- 启动 ----------- if __name__ == "__main__": ColorTransitionDetector().run()
最新发布
10-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值