You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实时数据采集下代码性能优化及亮度值异常问题求助

问题描述
  • 实验场景:基于2.5Hz采集频率的相机搭建物理实验实时演化追踪系统,核心需求是计算批次图像平均亮度并绘制实时曲线,系统效率需匹配实验节奏。
  • 程序逻辑:读取目标文件夹首张图像供用户选择感兴趣区域(ROI),随后计算单张图像及每img_round张图像批次的像素平均亮度,实时绘制批次平均亮度随迭代次数的变化曲线。
  • 异常表现:静态数据集上运行正常,但图像持续新增至文件夹的实验场景中,绘制的亮度值出现异常;同时希望优化代码运行性能。

用户提供的原始代码:

import os
import cv2
import numpy as np
import pyqtgraph as pg
from scipy.optimize import minimize_scalar
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
import time 
 
center = (0, 0)
radius = (0)
is_dragging_center = False
is_dragging_radius = False
global avg_brightness_per_img_round
avg_brightness_per_img_round = 0
 
img_round = 5
run_count = 0
 
brightness_history = []
std_history = []
func_history = []
global scatter_item
scatter_item = None
 
def update_display_image():
    global resized_image
    if resized_image is not None:
        display_image = resized_image.copy()
        cv2.circle(display_image, center, radius, (0, 255, 0), 2)
        cv2.circle(display_image, center, 5, (0, 0, 255), thickness=cv2.FILLED)
 
class UpdateDisplaySignal(QtCore.QObject):
    update_display_signal = QtCore.pyqtSignal()
 
update_display_signal_obj = UpdateDisplaySignal()
update_display_signal_obj.update_display_signal.connect(update_display_image)
 
 
def on_mouse(event, x, y, flags, param):
    global center, radius, is_dragging_center, is_dragging_radius
 
    if event == cv2.EVENT_LBUTTONDOWN:
        if np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) < 20:
            is_dragging_center = True
        else:
            is_dragging_radius = True
 
    elif event == cv2.EVENT_LBUTTONUP:
        is_dragging_center = False
        is_dragging_radius = False
 
    elif event == cv2.EVENT_MOUSEMOVE:
        if is_dragging_center:
            center = (x, y)
        elif is_dragging_radius:
            radius = int(np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2))
 
app = QtWidgets.QApplication([])
pw = pg.PlotWidget(title='Mean Brightness vs image round')
pw.setLabel('left', 'Mean Brightness')
pw.setLabel('bottom', 'Image round')
scatter = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 0, 0, 120))
line = pg.PlotDataItem(pen=pg.mkPen(color=(0,0,255), width=2))
pw.addItem(line)
pw.addItem(scatter)
 
def update_scatter():
    global scatter_item
    indices, values = zip(*enumerate(brightness_history, start=1))
    x = list(indices)
    y = list(values)
 
    if scatter_item is None:
        scatter_item = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 0, 0, 120))
        pw.addItem(scatter_item)
 
    if isinstance(x, int):  
        x = [x]  
 
    if len(x) > 1:
        line.setData(x=x, y=y)
        scatter.setData(x=x, y=y, symbol='o', size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 0, 0, 120))
 
        for i, (xi, yi) in enumerate(zip(x, y)):
            label = pg.TextItem(text=f'{yi:.2f}', anchor=(0, 0))
            label.setPos(xi, yi)
            pw.addItem(label)
            
win = QtWidgets.QMainWindow()
win.setCentralWidget(pw)
win.show()
 
path = r'C:\Users\blehe\Desktop\Betatron\images'
 
def calc_xray_count(image_path, center, radius):
    original_image = cv2.imread(image_path, cv2.IMREAD_ANYDEPTH)
 
    median_filtered_image = cv2.medianBlur(original_image, 5)
 
    mask = np.zeros(original_image.shape, dtype=np.uint8)
    cv2.circle(mask, center, radius, 255, thickness=cv2.FILLED)
 
    median_filtered_image += 1  # Avoid not counting black pixels in image
    result = cv2.bitwise_and(median_filtered_image, median_filtered_image, mask=mask)
 
    pixel_count = np.count_nonzero(result)
 
    img_brightness_sum = np.sum(result)
    img_var = np.var(result)
 
    if (pixel_count > 0):
        img_avg_brightness = (img_brightness_sum/pixel_count) -1 # Subtract back to real data
    else:
        img_avg_brightness = 0
 
    return img_avg_brightness, img_var
 
#-----------------------------------------------------------------------

image_files = []
for filename in os.listdir(path):
    if filename.endswith('.TIF'):
        image_files.append(os.path.join(path, filename))
 
first_image_path = image_files[0]
image = cv2.imread(first_image_path)
 
scale_percent = 60 
width = int(image.shape[1] * scale_percent / 100)
height = int(image.shape[0] * scale_percent / 100)
dim = (width, height)
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
colored_image = cv2.applyColorMap(gray_img, cv2.COLORMAP_PINK)
resized_image = cv2.resize(colored_image, dim, interpolation=cv2.INTER_AREA)
 
center = (resized_image.shape[1] // 2, resized_image.shape[0] // 2)
radius = min(resized_image.shape[1] // 3, resized_image.shape[0] // 3)
 
cv2.namedWindow("Adjust the circle (press 'Enter' to proceed)")
cv2.setMouseCallback("Adjust the circle (press 'Enter' to proceed)", on_mouse)
 
while True:
    display_image = resized_image.copy()
 
    cv2.circle(display_image, center, radius, (0, 255, 0), 2)
    cv2.circle(display_image, center, 5, (0, 0, 255), thickness=cv2.FILLED)
    cv2.imshow("Adjust the circle (press 'Enter' to proceed)", display_image)
 
    key = cv2.waitKey(1) & 0xFF
    if key == 13: 
        break
 
cv2.destroyAllWindows()
            
center = (int(center[0] / scale_percent * 100), int(center[1] / scale_percent * 100))
radius = int(radius / scale_percent * 100)
 
img_round_brightness_sum = 0
img_round_var_sum = 0
 
def process_images():
    global run_count, img_round_brightness_sum, img_round_var_sum
 
    while run_count < len(os.listdir(path)):   
        for i, image_path in enumerate(image_files, start=1):
            img_avg_brightness, img_var = calc_xray_count(image_path, center, radius)
            img_round_brightness_sum += img_avg_brightness
            img_round_var_sum += img_var
 
            run_count += 1
 
            if run_count % img_round == 0:
                avg_brightness_per_img_round = (img_round_brightness_sum/img_round)
                deviation_per_img_round = np.sqrt(img_round_var_sum/img_round)
 
                brightness_history.append(avg_brightness_per_img_round)
                std_history.append(deviation_per_img_round)
 
                update_scatter()

                img_round_brightness_sum = 0
                img_round_var_sum = 0
 
                img_avg_brightness = 0
                img_var = 0
 
                QtCore.QCoreApplication.processEvents()
                QtCore.QThread.msleep(100)
 
if __name__ == "__main__":
    timer = QtCore.QTimer() 
    timer.timeout.connect(process_images)
    timer.start(100)  
    app.exec_()

问题排查与修复

1. 实时图像新增未被检测的核心问题

原代码仅在启动时扫描一次文件夹生成image_files列表,后续新增的图像不会被纳入处理队列,导致程序反复处理旧图像,最终出现亮度值异常。修复方案:

  • 在处理函数内每次循环都重新扫描文件夹,获取最新图像列表
  • 记录已处理的文件名,避免重复处理同一图像

2. 绘图逻辑的冗余错误

原update_scatter函数每次都会重新创建所有文本标签,导致界面卡顿、标签重叠;同时存在scatter_item和全局scatter重复定义的问题。修复方案:

  • 仅更新新增数据对应的曲线和散点,避免重复创建绘图元素
  • 移除冗余的全局scatter_item,复用已创建的scatter对象
  • 若需要显示数值标签,仅添加最新批次的标签,而非全部重绘

性能优化建议

1. 图像处理优化

  • 预生成ROI掩码:掩码仅需根据用户选定的ROI创建一次,无需每次处理图像都重新生成,减少重复计算
  • 简化亮度计算逻辑:直接用掩码提取区域后计算均值,去掉median_filtered_image +=1再减1的冗余操作
  • 多线程分离:将图像读取、计算逻辑放在单独QThread中,避免阻塞UI线程,保证界面流畅

2. 循环与逻辑优化

  • 匹配采集频率设置扫描间隔:相机采集频率为2.5Hz,可将文件夹扫描间隔设为400ms,避免过度扫描浪费资源
  • 减少全局变量使用:用类封装程序状态(如已处理文件列表、批次累计值等),提升代码可维护性

修改后的完整代码

import os
import cv2
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets

class ImageProcessor(QtCore.QObject):
    update_plot_signal = QtCore.pyqtSignal(float)

    def __init__(self, path, center, radius, img_round=5):
        super().__init__()
        self.path = path
        self.center = center
        self.radius = radius
        self.img_round = img_round
        self.processed_files = set()
        self.batch_sum = 0.0
        self.batch_var_sum = 0.0
        self.batch_count = 0

        # 预生成掩码(后续根据图像尺寸动态调整)
        self.mask = None

    def process_new_images(self):
        current_files = {os.path.join(self.path, f) for f in os.listdir(self.path) if f.endswith('.TIF')}
        new_files = current_files - self.processed_files

        for img_path in sorted(new_files):
            try:
                img = cv2.imread(img_path, cv2.IMREAD_ANYDEPTH)
                if img is None:
                    continue

                # 动态生成掩码(适配不同图像尺寸)
                if self.mask is None or self.mask.shape != img.shape:
                    self.mask = np.zeros(img.shape, dtype=np.uint8)
                    cv2.circle(self.mask, self.center, self.radius, 255, thickness=cv2.FILLED)

                # 中值滤波+提取ROI
                filtered = cv2.medianBlur(img, 5)
                roi = filtered[self.mask == 255]
                
                if len(roi) == 0:
                    avg_brightness = 0.0
                    var = 0.0
                else:
                    avg_brightness = np.mean(roi)
                    var = np.var(roi)

                # 批次累计
                self.batch_sum += avg_brightness
                self.batch_var_sum += var
                self.batch_count += 1

                # 批次完成,发送更新信号
                if self.batch_count >= self.img_round:
                    batch_avg = self.batch_sum / self.img_round
                    self.update_plot_signal.emit(batch_avg)
                    # 重置批次统计
                    self.batch_sum = 0.0
                    self.batch_var_sum = 0.0
                    self.batch_count = 0

                self.processed_files.add(img_path)
            except Exception as e:
                print(f"处理图像 {img_path} 出错: {e}")
                continue

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, path):
        super().__init__()
        self.path = path
        self.center = (0,0)
        self.radius = 0
        self.init_ui()
        self.select_roi()
        self.start_processing()

    def init_ui(self):
        self.setWindowTitle("实时亮度追踪")
        self.pw = pg.PlotWidget(title='Mean Brightness vs Image Round')
        self.pw.setLabel('left', 'Mean Brightness')
        self.pw.setLabel('bottom', 'Image Round')
        self.line = pg.PlotDataItem(pen=pg.mkPen(color=(0,0,255), width=2))
        self.scatter = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255,0,0,120))
        self.pw.addItem(self.line)
        self.pw.addItem(self.scatter)
        self.setCentralWidget(self.pw)

        self.brightness_history = []
        self.round_count = 0

    def select_roi(self):
        # 获取首张图像
        image_files = [os.path.join(self.path, f) for f in os.listdir(self.path) if f.endswith('.TIF')]
        if not image_files:
            raise ValueError("文件夹中无TIF图像")
        first_img = cv2.imread(image_files[0])
        scale_percent = 60
        dim = (int(first_img.shape[1]*scale_percent/100), int(first_img.shape[0]*scale_percent/100))
        resized = cv2.resize(cv2.applyColorMap(cv2.cvtColor(first_img, cv2.COLOR_BGR2GRAY), cv2.COLORMAP_PINK), dim)
        
        # 初始化ROI
        self.center = (resized.shape[1]//2, resized.shape[0]//2)
        self.radius = min(resized.shape[1]//3, resized.shape[0]//3)
        is_dragging_center = False
        is_dragging_radius = False

        def mouse_callback(event, x, y, flags, param):
            nonlocal is_dragging_center, is_dragging_radius
            if event == cv2.EVENT_LBUTTONDOWN:
                if np.sqrt((x-self.center[0])**2 + (y-self.center[1])**2) <20:
                    is_dragging_center = True
                else:
                    is_dragging_radius = True
            elif event == cv2.EVENT_LBUTTONUP:
                is_dragging_center = False
                is_dragging_radius = False
            elif event == cv2.EVENT_MOUSEMOVE:
                if is_dragging_center:
                    self.center = (x,y)
                elif is_dragging_radius:
                    self.radius = int(np.sqrt((x-self.center[0])**2 + (y-self.center[1])**2))

        cv2.namedWindow("Adjust ROI (Press Enter to confirm)")
        cv2.setMouseCallback("Adjust ROI (Press Enter to confirm)", mouse_callback)
        
        while True:
            display = resized.copy()
            cv2.circle(display, self.center, self.radius, (0,255,0),2)
            cv2.circle(display, self.center,5,(0,0,255),cv2.FILLED)
            cv2.imshow("Adjust ROI (Press Enter to confirm)", display)
            if cv2.waitKey(1) &0xFF ==13:
                break
        cv2.destroyAllWindows()

        # 还原ROI到原始图像尺寸
        self.center = (int(self.center[0]/scale_percent*100), int(self.center[1]/scale_percent*100))
        self.radius = int(self.radius/scale_percent*100)

    def start_processing(self):
        self.processor = ImageProcessor(self.path, self.center, self.radius)
        self.processor.update_plot_signal.connect(self.update_plot)

        # 设置定时器,匹配相机2.5Hz采集频率,每400ms扫描一次
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.processor.process_new_images)
        self.timer.start(400)

    def update_plot(self, batch_avg):
        self.round_count +=1
        self.brightness_history.append(batch_avg)
        x = list(range(1, self.round_count+1))
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:09:58