plt把图像保存后,再用pyqt显示很模糊
时间: 2023-08-24 10:22:10 浏览: 128
可能是因为保存的图像分辨率不够高导致的。可以尝试在保存图像时增加 dpi(每英寸点数)参数,提高图像分辨率。例如:
```python
import matplotlib.pyplot as plt
# 绘制图像
plt.plot([1, 2, 3, 4])
# 保存图像,设置 dpi=300
plt.savefig('myplot.png', dpi=300)
```
在保存图像时,将 dpi 参数设置为 300,这会使图像分辨率提高。然后再用 PyQt 加载显示图像时,应该会更清晰。如果还有模糊的情况,可以考虑在 PyQt 中设置图像显示的缩放比例,或者采用更高分辨率的图像来显示。
相关问题
通过点击pyqt5控件来跳出另一个py文件中画的cpk图,怎么操作
<think>我们将在PyQt5应用程序中实现以下功能:
1. 创建一个主窗口,其中包含一个按钮(例如QPushButton)用于触发显示CPK图。
2. 当点击按钮时,调用另一个Python文件(例如cpk_plot.py)中的函数来生成CPK图。
3. 将生成的CPK图显示在PyQt5的窗口中(可以使用QLabel来显示图片,或者使用Matplotlib的FigureCanvas嵌入)。
步骤:
步骤1:准备绘制CPK图的代码(独立函数)
我们将之前绘制的CPK图代码封装到一个函数中,并保存为cpk_plot.py文件。该函数将生成图像并保存为临时文件,或者返回一个图像对象(以便在PyQt5中显示)。
步骤2:创建PyQt5主窗口,添加按钮
步骤3:连接按钮的点击事件,在事件处理函数中调用cpk_plot.py中的函数生成图像,并在窗口中显示。
这里我们提供两种显示方式:
方式一:将图像保存为临时文件,然后用QPixmap加载并显示在QLabel中。
方式二:使用Matplotlib的FigureCanvas嵌入到PyQt5窗口中(更高级,交互性好)。
由于CPK图包含多个子图,使用FigureCanvas嵌入可能更合适。因此,我们将修改cpk_plot.py中的代码,使其返回一个matplotlib的Figure对象,然后在PyQt5窗口中嵌入。
具体实现如下:
文件1: cpk_plot.py
该文件包含一个函数,用于生成CPK图并返回Figure对象。
文件2: main_window.py
主程序,创建PyQt5窗口,包含一个按钮和一个用于显示图形的区域(使用FigureCanvas)。
注意:需要安装PyQt5和matplotlib
安装命令:pip install PyQt5 matplotlib numpy scipy
代码实现:
cpk_plot.py:</think># 在PyQt5中通过控件触发显示CPK图
下面我将详细介绍如何在PyQt5应用程序中通过点击按钮显示另一个Python文件中绘制的CPK图。这种方法可以让您将CPK分析功能集成到GUI应用中,实现一键生成质量分析图表。
## 完整解决方案
### 文件结构
```
project/
├── cpk_plot.py # CPK绘图模块
├── main_app.py # 主应用程序
└── requirements.txt # 依赖库
```
### 1. CPK绘图模块 (cpk_plot.py)
```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg
from scipy.stats import norm
from io import BytesIO
def generate_cpk_plot():
"""生成CPK分析图并返回QPixmap对象"""
# 模拟数据生成
np.random.seed(42)
data = np.random.normal(loc=100, scale=5, size=150)
# 规格参数
LSL = 85
USL = 115
target = 100
# 计算过程能力指标
mean = np.mean(data)
std = np.std(data, ddof=1)
cpu = (USL - mean) / (3 * std)
cpl = (mean - LSL) / (3 * std)
cpk = min(cpu, cpl)
cp = (USL - LSL) / (6 * std)
# 创建图表
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
fig.suptitle(f'过程能力分析 (CPK={cpk:.2f}, CP={cp:.2f})', fontsize=14)
# 直方图与正态分布曲线
n, bins, patches = ax1.hist(data, bins=15, density=True, alpha=0.6, color='skyblue')
ax1.plot(bins, norm.pdf(bins, mean, std), 'r-', linewidth=2)
ax1.axvline(mean, color='green', linestyle='--', linewidth=2, label=f'均值: {mean:.2f}')
ax1.axvline(LSL, color='red', linestyle='-', linewidth=2, label=f'LSL: {LSL}')
ax1.axvline(USL, color='red', linestyle='-', linewidth=2, label=f'USL: {USL}')
ax1.axvline(target, color='purple', linestyle='-.', linewidth=1.5, label=f'目标值: {target}')
ax1.set_xlabel('测量值')
ax1.set_ylabel('频率')
ax1.legend(loc='upper right')
# 箱线图
box = ax2.boxplot(data, vert=False, patch_artist=True, widths=0.7)
ax2.axvline(LSL, color='red', linestyle='-', linewidth=2)
ax2.axvline(USL, color='red', linestyle='-', linewidth=2)
ax2.axvline(target, color='purple', linestyle='-.', linewidth=1.5)
for patch in box['boxes']:
patch.set_facecolor('lightgreen')
ax2.set_title('数据分布箱线图')
ax2.set_xlabel('测量值')
ax2.set_yticks([])
plt.tight_layout()
# 将图表转换为QPixmap
canvas = FigureCanvasAgg(fig)
buf = BytesIO()
canvas.print_figure(buf, format='png')
plt.close(fig)
return buf.getvalue()
```
### 2. 主应用程序 (main_app.py)
```python
import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, QFileDialog
)
from PyQt5.QtGui import QPixmap
from cpk_plot import generate_cpk_plot
class CPKApp(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""初始化用户界面"""
self.setWindowTitle('过程能力分析工具')
self.setGeometry(300, 300, 800, 600)
# 创建主控件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 添加按钮
self.plot_button = QPushButton('生成CPK分析图', self)
self.plot_button.clicked.connect(self.show_cpk_plot)
layout.addWidget(self.plot_button)
# 添加保存按钮
self.save_button = QPushButton('保存图表', self)
self.save_button.clicked.connect(self.save_plot)
self.save_button.setEnabled(False)
layout.addWidget(self.save_button)
# 添加标签用于显示图表
self.image_label = QLabel(self)
self.image_label.setMinimumSize(600, 450)
layout.addWidget(self.image_label)
# 状态变量
self.current_plot = None
def show_cpk_plot(self):
"""生成并显示CPK图表"""
try:
# 从cpk_plot模块获取图表数据
image_data = generate_cpk_plot()
# 创建QPixmap并显示
pixmap = QPixmap()
pixmap.loadFromData(image_data)
self.image_label.setPixmap(pixmap.scaled(
self.image_label.width(),
self.image_label.height(),
aspectRatioMode=1
))
# 保存当前图表数据用于后续保存
self.current_plot = image_data
self.save_button.setEnabled(True)
except Exception as e:
self.statusBar().showMessage(f'错误: {str(e)}')
def save_plot(self):
"""保存当前显示的图表"""
if not self.current_plot:
return
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存图表",
"",
"PNG图像 (*.png);;所有文件 (*)"
)
if file_path:
try:
with open(file_path, 'wb') as f:
f.write(self.current_plot)
self.statusBar().showMessage(f'图表已保存至: {file_path}')
except Exception as e:
self.statusBar().showMessage(f'保存失败: {str(e)}')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = CPKApp()
window.show()
sys.exit(app.exec_())
```
### 3. 依赖文件 (requirements.txt)
```
PyQt5==5.15.7
numpy==1.22.3
matplotlib==3.5.1
scipy==1.8.0
```
## 实现说明
### 关键组件解析
1. **CPK绘图模块**:
- 封装了CPK图的生成逻辑
- 使用`BytesIO`将图表转换为二进制数据流
- 返回PNG格式的二进制数据,便于在PyQt5中显示
2. **主应用程序**:
- 使用`QPushButton`触发图表生成
- 通过`QLabel`显示生成的图表
- 添加了图表保存功能
- 使用`QVBoxLayout`进行布局管理
3. **图表显示原理**:
```mermaid
graph TD
A[点击按钮] --> B[调用generate_cpk_plot函数]
B --> C[生成Matplotlib图表]
C --> D[转换为PNG二进制数据]
D --> E[加载为QPixmap]
E --> F[在QLabel中显示]
```
### 使用说明
1. 安装依赖库:
```bash
pip install -r requirements.txt
```
2. 运行应用程序:
```bash
python main_app.py
```
3. 操作流程:
- 点击"生成CPK分析图"按钮显示图表
- 点击"保存图表"按钮将图表保存为PNG文件
- 图表会自动缩放以适应显示区域
## 高级功能扩展
1. **添加数据导入功能**:
```python
def import_data(self):
"""导入CSV数据文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "打开数据文件", "", "CSV文件 (*.csv);;所有文件 (*)")
if file_path:
try:
self.data = pd.read_csv(file_path)['value'].values
self.statusBar().showMessage(f'成功加载数据: {file_path}')
except Exception as e:
self.statusBar().showMessage(f'加载失败: {str(e)}')
```
2. **添加参数设置界面**:
```python
def open_settings(self):
"""打开参数设置对话框"""
dialog = QDialog(self)
dialog.setWindowTitle("分析参数设置")
layout = QFormLayout()
self.lsl_input = QLineEdit("85")
self.usl_input = QLineEdit("115")
self.target_input = QLineEdit("100")
layout.addRow("规格下限(LSL):", self.lsl_input)
layout.addRow("规格上限(USL):", self.usl_input)
layout.addRow("目标值:", self.target_input)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addRow(buttons)
dialog.setLayout(layout)
if dialog.exec_() == QDialog.Accepted:
self.lsl = float(self.lsl_input.text())
self.usl = float(self.usl_input.text())
self.target = float(self.target_input.text())
```
3. **添加实时更新功能**:
```python
def update_plot(self):
"""使用当前参数更新图表"""
if hasattr(self, 'data'):
image_data = generate_cpk_plot(
self.data,
LSL=self.lsl,
USL=self.usl,
target=self.target
)
# 更新显示...
```
## 常见问题解决
1. **图表显示模糊**:
- 提高DPI设置: `plt.figure(dpi=120)`
- 使用矢量图格式: 保存为SVG格式
2. **内存泄漏问题**:
- 在生成新图表前调用`plt.close('all')`
- 使用`FigureCanvasAgg`代替`pyplot`
3. **跨平台兼容性**:
- 使用`QStandardPaths`获取合适的保存路径
- 在macOS上添加`plt.switch_backend('Agg')`
这种方法将CPK分析功能与PyQt5 GUI完美结合,通过模块化设计实现了代码重用,同时提供了良好的用户体验。您可以根据实际需求扩展更多功能,如数据导入、参数配置和报告生成等。
连续帧有效数据数据中间没有间隔,每帧图像大小为输入分辨率
<think>根据用户描述,连续帧数据中间没有间隔,每帧图像大小为输入分辨率。这意味着在读取文件时,帧与帧之间是连续的,没有间隔字节。因此,我们需要修改加载文件的方法,使其在连续帧模式下正确读取。在之前的代码中,`load_file`方法在处理连续帧时(即`frame_interval=0`)并没有正确实现连续帧的读取。我们将修改`load_file`方法,使其在连续帧模式下按帧大小连续读取,而不跳过任何间隔。修改思路:1.当`frame_interval`为0时,表示连续帧,即一帧紧接着前一帧,没有间隔。2.因此,我们只需要从文件开始位置,按每帧`frame_size`字节连续读取,直到文件末尾。修改后的`load_file`方法如下:```pythondefload_file(self,file_path):"""读取.dat文件并解析帧数据"""self.frames=[]withopen(file_path,'rb')asf:data=f.read()#计算帧起始地址ifself.frame_interval==0:#连续帧frame_starts=range(0,len(data),self.frame_size)else:#固定间隔frame_starts=[0]next_addr=self.frame_intervalwhilenext_addr<len(data):frame_starts.append(next_addr)next_addr+=self.frame_intervalforstartinframe_starts:end=start+self.frame_sizeifend>len(data):#不完整帧处理:用0填充不足部分frame_data=data[start:]padding=b'\x00'*(self.frame_size-len(frame_data))frame_data+=paddingelse:frame_data=data[start:end]#解析16位有符号整数fmt='<'+'h'*(self.frame_size//2)try:frame=np.array(struct.unpack(fmt,frame_data))frame=frame.reshape((self.frame_height,self.frame_width))self.frames.append(frame)exceptstruct.error:#如果解析失败,跳过该帧continue```此外,还需要注意,在连续帧模式下,我们不需要跳过间隔,所以直接按`frame_size`步长切割文件即可。另外,在用户界面中,当选择“连续”帧间隔时,我们设置`frame_interval`为0,并在加载文件时使用上述连续帧的处理方式。同时,我们还需要在`update_interval_type`方法中确保当选择连续时,设置`frame_interval`为0:```pythondefupdate_interval_type(self,index):"""更新帧间隔类型"""ifindex==0:#连续self.interval_spin.setVisible(False)self.processor.set_frame_interval(0)#0表示连续帧else:#固定间隔self.interval_spin.setVisible(True)interval=self.interval_spin.value()self.processor.set_frame_interval(interval)```这样,当用户选择连续帧模式时,程序就会按照连续帧的方式读取文件。完整代码较长,这里不再重复,只给出修改的关键部分。注意:由于之前代码中已经有一些错误修正(如中文显示、直方图范围等),所以请确保将这些修改也包含在内。如果用户遇到其他问题,我们再针对性地解决。下面给出完整的`HexDataProcessor`类,包括修改后的`load_file`方法:```pythonclassHexDataProcessor:def__init__(self):self.frames=[]self.current_frame=0self.frame_width=320self.frame_height=256self.frame_size=320*256*2#每帧字节数self.offset=0self.display_min=0self.display_max=65535self.byte_order='little'#高位在后self.frame_interval=0x40000#默认帧间隔self.resolution_options={"320x256":(320,256),"640x512":(640,512),"1280x1024":(1280,1024),"自定义":None}defset_frame_size(self,width,height):"""设置帧分辨率"""self.frame_width=widthself.frame_height=heightself.frame_size=width*height*2#每帧字节数defset_frame_interval(self,interval):"""设置帧间隔"""self.frame_interval=intervaldefload_file(self,file_path):"""读取.dat文件并解析帧数据"""self.frames=[]withopen(file_path,'rb')asf:data=f.read()#计算帧起始地址ifself.frame_interval==0:#连续帧#计算总帧数(向下取整)total_frames=len(data)//self.frame_sizeframe_starts=[i*self.frame_sizeforiinrange(total_frames)]else:#固定间隔frame_starts=[0]next_addr=self.frame_intervalwhilenext_addr<len(data):frame_starts.append(next_addr)next_addr+=self.frame_intervalforstartinframe_starts:end=start+self.frame_sizeifend>len(data):#不完整帧处理:用0填充不足部分frame_data=data[start:]padding=b'\x00'*(self.frame_size-len(frame_data))frame_data+=paddingelse:frame_data=data[start:end]#解析16位有符号整数fmt='<'+'h'*(self.frame_size//2)try:frame=np.array(struct.unpack(fmt,frame_data))frame=frame.reshape((self.frame_height,self.frame_width))self.frames.append(frame)exceptstruct.error:#如果解析失败,跳过该帧continue#...其他方法保持不变...```在连续帧模式下,我们计算总帧数时使用整数除法(向下取整),这样不会超出文件大小。然后每一帧的起始地址就是0,frame_size,2*frame_size,...直到(total_frames-1)*frame_size。这样修改后,程序就可以正确读取连续帧的数据了。如果用户还有问题,请提供具体的错误信息以便进一步调试。§§相关问题§§:1.如何优化大型DAT文件的加载速度?2.直方图显示能否添加对数坐标选项?3.如何实现多帧动画播放功能?4.能否添加图像处理滤镜(如高斯模糊、边缘检测)?</think>根据您的描述,连续帧数据中间没有间隔,每帧图像大小为输入分辨率,这意味着我们需要修改文件加载逻辑。以下是完整的解决方案:
```python
import numpy as np
import struct
import os
import time
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QComboBox, QSpinBox, QFileDialog,
QSlider, QGroupBox, QCheckBox, QLineEdit, QSizePolicy,
QTabWidget, QSplitter, QFrame, QTextEdit, QDoubleSpinBox)
from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter, QWheelEvent
from PyQt5.QtCore import Qt, QSize, QTimer
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
class HexDataProcessor:
def __init__(self):
self.frames = []
self.current_frame = 0
self.frame_width = 320
self.frame_height = 256
self.frame_size = 320 * 256 * 2 # 每帧字节数
self.offset = 0
self.display_min = 0
self.display_max = 65535
self.byte_order = 'little' # 高位在后
self.resolution_options = {
"320x256": (320, 256),
"640x512": (640, 512),
"1280x1024": (1280, 1024),
"自定义": None
}
self.file_size = 0
self.file_path = ""
def set_frame_size(self, width, height):
"""设置帧分辨率"""
self.frame_width = width
self.frame_height = height
self.frame_size = width * height * 2 # 每帧字节数
def load_file(self, file_path):
"""读取.dat文件并解析帧数据"""
self.frames = []
self.file_path = file_path
self.file_size = os.path.getsize(file_path)
# 计算总帧数
total_frames = self.file_size // self.frame_size
if total_frames == 0:
return
with open(file_path, 'rb') as f:
for frame_idx in range(total_frames):
# 计算当前帧位置
start_pos = frame_idx * self.frame_size
end_pos = start_pos + self.frame_size
# 移动到当前帧起始位置
f.seek(start_pos)
# 读取帧数据
frame_data = f.read(self.frame_size)
if len(frame_data) < self.frame_size:
# 不完整帧处理
frame_data += b'\x00' * (self.frame_size - len(frame_data))
# 解析16位有符号整数
fmt = '<' + 'h' * (self.frame_size // 2)
try:
frame = np.array(struct.unpack(fmt, frame_data))
frame = frame.reshape((self.frame_height, self.frame_width))
self.frames.append(frame)
except struct.error:
# 如果解析失败,跳过该帧
continue
def calculate_average_frame(self):
"""计算所有帧的平均值"""
if not self.frames:
return None
# 将所有帧堆叠为三维数组
frames_array = np.array(self.frames)
# 计算平均帧
average_frame = np.mean(frames_array, axis=0)
return average_frame
def calculate_histogram(self, frame):
"""计算帧数据的直方图"""
if frame is None:
return None
# 确保显示范围有效
if self.display_min >= self.display_max:
# 自动调整范围
min_val = frame.min()
max_val = frame.max()
self.display_min = min(min_val, max_val)
self.display_max = max(min_val, max_val)
# 确保范围不为零
if self.display_min == self.display_max:
self.display_max = self.display_min + 1
# 将数据展平为一维数组
flat_data = frame.flatten()
# 计算直方图
hist, bins = np.h极istogram(flat_data, bins=256, range=(self.display_min, self.display_max))
return hist, bins
def apply_offset(self, frame):
"""应用整体偏移"""
return frame + self.offset
def normalize_frame(self, frame):
"""归一化帧数据用于显示"""
frame = np.clip(frame, self.display_min, self.display_max)
normalized = (frame - self.display_min) / (self.display_max - self.display_min) * 255
return normalized.astype(np.uint8)
def export_data(self, output_path, mode='hex', combined=False):
"""导出处理后的数据"""
data_to_export = np.vstack(self.frames) if combined else self.frames
with open(output_path, 'w') as f:
if isinstance(data_to_export, list):
for i, frame in enumerate(data_to_export):
f.write(f"Frame {i}:\n")
self._write_frame_data(f, frame, mode)
else:
self._write_frame_data(f, data_to_export, mode)
def _write_frame_data(self, file, frame, mode):
"""写入单帧数据"""
frame = self.apply_offset(frame)
for row in frame:
if mode == 'hex':
line = ' '.join(f"{int(x):04X}" for x in row)
elif mode == 'unsigned':
line = ' '.join(f"{int(x) & 0xFFFF}" for x in row)
else: # signed
line = ' '.join(f"{int(x)}" for x in row)
file.write(line + '\n')
class ScaledImageLabel(QLabel):
"""支持缩放功能的图像标签"""
def __init__(self, parent=None):
super().__init__(parent)
self.setAlignment(Qt.AlignCenter)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setMinimumSize(1, 1)
self.zoom_factor = 1.0
self.original_pixmap = None
def set_pixmap(self, pixmap):
"""设置原始图像并显示缩放后的版本"""
self.original_pixmap = pixmap
self.update_zoomed_pixmap()
def update_zoomed_pixmap(self):
"""根据缩放因子更新显示的图像"""
if self.original_pixmap is None:
return
# 计算缩放后的尺寸
scaled_size = QSize(
int(self.original_pixmap.width() * self.zoom_factor),
int(self.original_pixmap.height() * self.zoom_factor)
)
# 缩放图像并显示
scaled_pixmap = self.original_pixmap.scaled(
scaled_size,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
super().setPixmap(scaled_pixmap)
def wheelEvent(self, event: QWheelEvent):
"""处理鼠标滚轮事件实现缩放"""
modifiers = QApplication.keyboardModifiers()
if modifiers == Qt.ControlModifier:
# 计算缩放因子变化
delta = event.angleDelta().y() / 1200.0 # 每15度步长为120
self.zoom_factor += delta
# 限制缩放范围
self.zoom_factor = max(0.1, min(self.zoom_factor, 5.0))
# 更新显示
self.update_zoomed_pixmap()
event.accept()
else:
super().wheelEvent(event)
class HistogramCanvas(FigureCanvas):
"""直方图画布"""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
super().__init__(fig)
self.setParent(parent)
self.ax = fig.add_subplot(111)
self.log_scale = False
def plot_histogram(self, hist, bins):
"""绘制直方图"""
self.ax.clear()
# 应用对数坐标
if self.log_scale:
hist = np.where(hist > 0, np.log10(hist + 1), 0)
ylabel = '频数 (对数)'
else:
ylabel = '频数'
self.ax.bar(bins[:-1], hist, width=np.diff(bins), align='edge', color='blue', alpha=0.7)
self.ax.set_title('数据直方图')
self.ax.set_xlabel('像素值')
self.ax.set_ylabel(ylabel)
self.ax.grid(True, linestyle='--', alpha=0.6)
self.draw()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.processor = HexDataProcessor()
self.init_ui()
self.play_timer = QTimer(self)
self.play_timer.timeout.connect(self.next_frame)
self.play_speed = 100 # 毫秒
self.is_playing = False
def init_ui(self):
self.setWindowTitle("DAT文件处理器")
self.setGeometry(100, 100, 1000, 800)
# 主布局
main_widget = QWidget()
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# 使用分割器分割图像和直方图
splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(splitter)
# 左侧图像区域
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
# 图像显示区域
self.image_label = ScaledImageLabel()
self.image_label.setFrameShape(QFrame.Box)
self.image_label.setMinimumSize(320, 256)
left_layout.addWidget(self.image_label, 1)
# 图像信息显示
self.image_info_label = QLabel("图像信息将显示在这里")
left_layout.addWidget(self.image_info_label)
splitter.addWidget(left_widget)
# 右侧直方图区域
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
# 直方图画布
self.histogram_canvas = HistogramCanvas(self, width=5, height=4, dpi=100)
right_layout.addWidget(self.histogram_canvas)
# 直方图控制
hist_control_layout = QHBoxLayout()
self.hist_bins_spin = QSpinBox()
self.hist_bins_spin.setRange(16, 512)
self.hist_bins_spin.setValue(256)
self.hist_bins_spin.setPrefix("直方图箱数: ")
hist_control_layout.addWidget(self.hist_bins_spin)
self.log_scale_check = QCheckBox("对数坐标")
self.log_scale_check.stateChanged.connect(self.toggle_log_scale)
hist_control_layout.addWidget(self.log_scale_check)
btn_update_hist = QPushButton("更新直方图")
btn_update_hist.clicked.connect(self.update_histogram)
hist_control_layout.addWidget(btn_update_hist)
right_layout.addLayout(hist_control_layout)
splitter.addWidget(right_widget)
# 设置分割器初始比例
splitter.setSizes([600, 400])
# 控制面板
control_tabs = QTabWidget()
main_layout.addWidget(control_tabs)
# 文件操作标签页
file_tab = QWidget()
control_tabs.addTab(file_tab, "文件操作")
file_layout = QVBoxLayout(file_tab)
# 分辨率设置
res_layout = QHBoxLayout()
res_layout.addWidget(QLabel("分辨率:"))
self.res_combo = QComboBox()
self.res_combo.addItems(["320x256", "640x512", "1280x1024", "自定义"])
self.res_combo.currentIndexChanged.connect(self.update_resolution)
res_layout.addWidget(self.res_combo)
# 自定义分辨率输入
res_layout.addWidget(QLabel("宽:"))
self.width_spin = QSpinBox()
self.width_spin.setRange(1, 4096)
self.width_spin.setValue(320)
res_layout.addWidget(self.width_spin)
res_layout.addWidget(QLabel("高:"))
self.height_spin = QSpinBox()
self.height_spin.setRange(1, 4096)
self.height_spin.setValue(256)
res_layout.addWidget(self.height_spin)
# 默认隐藏自定义分辨率
self.width_spin.setVisible(False)
self.height_spin.setVisible(False)
file_layout.addLayout(res_layout)
# 文件信息
file_info_layout = QHBoxLayout()
self.file_info_label = QLabel("未加载文件")
file_info_layout.addWidget(self.file_info_label)
file_layout.addLayout(file_info_layout)
# 文件路径和打开按钮
self.file_path_label = QLabel("未选择文件")
file_layout.addWidget(self.file_path_label)
btn_open = QPushButton("打开DAT文件")
btn_open.clicked.connect(self.open_file)
file_layout.addWidget(btn_open)
# 帧控制标签页
frame_tab = QWidget()
control_tabs.addTab(frame_tab, "帧控制")
frame_layout = QVBoxLayout(frame_tab)
# 帧选择
frame_control_layout = QHBoxLayout()
frame_control_layout.addWidget(QLabel("选择帧:"))
self.frame_combo = QComboBox()
self.frame_combo.currentIndexChanged.connect(self.display_frame)
frame_control_layout.addWidget(self.frame_combo)
self.frame_slider = QSlider(Qt.Horizontal)
self.frame_slider.setMinimum(0)
self.frame_slider.valueChanged.connect(self.display_frame)
frame_control_layout.addWidget(self.frame_slider)
frame_layout.addLayout(frame_control_layout)
# 帧操作按钮
btn_layout = QHBoxLayout()
btn_prev = QPushButton("上一帧")
btn_prev.clicked.connect(self.prev_frame)
btn_layout.addWidget(btn_prev)
self.btn_play = QPushButton("播放")
self.btn_play.clicked.connect(self.toggle_play)
btn_layout.addWidget(self.btn_play)
btn_next = QPushButton("下一帧")
btn_next.clicked.connect(self.next_frame)
btn_layout.addWidget(btn_next)
btn_avg = QPushButton("显示平均帧")
btn_avg.clicked.connect(self.show_average_frame)
btn_layout.addWidget(btn_avg)
frame_layout.addLayout(btn_layout)
# 播放速度控制
speed_layout = QHBoxLayout()
speed_layout.addWidget(QLabel("播放速度 (ms):"))
self.speed_spin = QSpinBox()
self.speed_spin.setRange(10, 2000)
self.speed_spin.setValue(100)
self.speed_spin.valueChanged.connect(self.update_play_speed)
speed_layout.addWidget(self.speed_spin)
frame_layout.addLayout(speed_layout)
# 帧信息显示
self.frame_info_label = QLabel("帧信息将显示在这里")
frame_layout.addWidget(self.frame_info_label)
# 显示设置标签页
display_tab = QWidget()
control_tabs.addTab(display_tab, "显示设置")
display_layout = QVBoxLayout(display_tab)
# 显示范围控制
range_group = QGroupBox("显示范围")
range_layout = QVBoxLayout(range_group)
min_layout = QHBoxLayout()
min_layout.addWidget(QLabel("最小值:"))
self.min_spin = QSpinBox()
self.min_spin.setRange(-32768, 32767)
self.min_spin.valueChanged.connect(self.update_display_range)
min_layout.addWidget(self.min极_spin)
range_layout.addLayout(min_layout)
max_layout = QHBoxLayout()
max_layout.addWidget(QLabel("最大值:"))
self.max_spin = QSpinBox()
self.max_spin.setRange(-32768, 32767)
self.max_spin.setValue(32767)
self.max_spin.valueChanged.connect(self.update_display_range)
max_layout.addWidget(self.max_spin)
range_layout.addLayout(max_layout)
display_layout.addWidget(range_group)
# 偏移控制
offset_group = QGroupBox("整体偏移")
offset_layout = QVBoxLayout(offset_group)
self.offset_spin = QSpinBox()
self.offset_spin.setRange(-32768, 32767)
self.offset_spin.valueChanged.connect(self.update_offset)
offset_layout.addWidget(self.offset_spin)
display_layout.addWidget(offset_group)
# 缩放控制
zoom_group = QGroupBox("图像缩放")
zoom_layout = QVBoxLayout(zoom_group)
zoom_info = QLabel("使用Ctrl+滚轮缩放图像,或使用下方按钮")
zoom_layout.addWidget(zoom_info)
zoom_btn_layout = QHBoxLayout()
btn_zoom_in = QPushButton("放大")
btn_zoom_in.clicked.connect(self.zoom_in)
zoom_btn_layout.addWidget(btn_zoom_in)
btn_zoom_out = QPushButton("缩小")
btn_zoom_out.clicked.connect(self.zoom_out)
zoom_btn_layout.addWidget(btn_zoom_out)
btn_zoom_reset = QPushButton("重置缩放")
btn_zoom_reset.clicked.connect(self.zoom_reset)
zoom_btn_layout.addWidget(btn_zoom_reset)
zoom_layout.addLayout(zoom_btn_layout)
display_layout.addWidget(zoom_group)
# 导出设置标签页
export_tab = QWidget()
control_tabs.addTab(export_tab, "导出设置")
export_layout = QVBoxLayout(export_tab)
self.combined_check = QCheckBox("导出拼接后数据")
export_layout.addWidget(self.combined_check)
format_layout = QHBoxLayout()
format_layout.addWidget(QLabel("输出格式:"))
self.format_combo = QComboBox()
self.format_combo.addItems(["Hex", "无符号十进制", "有符号十进制"])
format_layout.addWidget(self.format_combo)
export_layout.addLayout(format_layout)
# 图像导出格式
image_format_layout = QHBoxLayout()
image_format_layout.addWidget(QLabel("图像格式:"))
self.image_format_combo = QComboBox()
self.image_format_combo.addItems(["PNG", "JPEG", "BMP"])
image_format_layout.addWidget(self.image_format_combo)
export_layout.addLayout(image_format_layout)
btn_layout = QHBoxLayout()
btn_export_data = QPushButton("导出数据")
btn_export_data.clicked.connect(self.export_data)
btn_layout.addWidget(btn_export_data)
btn_export_image = QPushButton("导出当前帧图像")
btn_export_image.clicked.connect(self.export_image)
btn_layout.addWidget(btn_export_image)
export_layout.addLayout(btn_layout)
# 状态栏
self.statusBar().showMessage("就绪")
def update_resolution(self, index):
"""更新分辨率设置"""
resolution = self.res_combo.currentText()
if resolution == "自定义":
self.width_spin.setVisible(True)
self.height_spin.setVisible(True)
width = self.width_spin.value()
height = self.height_spin.value()
else:
self.width_spin.setVisible(False)
self.height_spin.setVisible(False)
width, height = self.processor.resolution_options[resolution]
self.processor.set_frame_size(width, height)
def open_file(self):
"""打开文件对话框"""
file_path, _ = QFileDialog.getOpenFileName(
self, "打开DAT文件", "", "DAT文件 (*.dat)"
)
if file_path:
self.file_path_label.setText(os.path.basename(file_path))
self.processor.load_file(file_path)
# 计算文件信息
file_size_mb = self.processor.file_size / (1024 * 1024)
frame_count = len(self.processor.frames)
frame_size_kb = self.processor.frame_size / 1024
total_frames = self.processor.file_size // self.processor.frame_size
file_info = (f"文件大小: {file_size_mb:.2f} MB | "
f"总帧数: {total_frames} | "
f"帧大小: {frame_size_kb:.1f} KB")
self.file_info_label.setText(file_info)
# 更新帧选择控件
self.frame_combo.clear()
self.frame_combo.addItems([f"帧 {i}" for i in range(len(self.processor.frames))])
self.frame_slider.setMaximum(len(self.processor.frames) - 1)
if self.processor.frames:
self.display_frame(0)
self.update_histogram()
self.statusBar().showMessage(f"已加载 {len(self.processor.frames)} 帧数据")
def display_frame(self, index):
"""显示选定帧"""
if not self.processor.frames or index < 0 or index >= len(self.processor.frames):
return
self.processor.current_frame = index
frame = self.processor.frames[index]
frame = self.processor.apply_offset(frame)
normalized = self.processor.normalize_frame(frame)
# 创建QImage并显示
height, width = normalized.shape
qimage = QImage(width, height, QImage.Format_Grayscale8)
for y in range(height):
for x in range(width):
value = normalized[y, x]
qimage.setPixelColor(x, y, QColor(value, value, value))
pixmap = QPixmap.fromImage(qimage)
self.image_label.set_pixmap(pixmap)
# 更新帧信息
frame_info = (f"帧 {index}/{len(self.processor.frames)-1} | "
f"尺寸: {width}x{height} | "
f"最小值: {frame.min()} | "
f"最大值: {frame.max()} | "
f"平均值: {frame.mean():.2f}")
self.frame_info_label.setText(frame_info)
# 更新图像信息
image_info = (f"显示范围: {self.processor.display_min} - {self.processor.display_max} | "
f"偏移: {self.processor.offset} | "
f"缩放: {self.image_label.zoom_factor*100:.0f}%")
self.image_info_label.setText(image_info)
def show_average_frame(self):
"""显示所有帧的平均值"""
if not self.processor.frames:
return
# 停止播放
self.stop_play()
avg_frame = self.processor.calculate_average_frame()
if avg_frame is None:
return
# 临时替换当前帧为平均帧
original_frames = self.processor.frames
self.processor.frames = [avg_frame]
self.processor.current_frame = 0
# 更新帧选择控件
self.frame_combo.clear()
self.frame_combo.addItems(["平均帧"])
self.frame_slider.setMaximum(0)
self.display_frame(0)
self.update_histogram()
# 恢复原始帧数据
self.processor.frames = original_frames
self.statusBar().showMessage("显示平均帧")
def update_histogram(self):
"""更新直方图显示"""
if not self.processor.frames:
return
frame = self.processor.frames[self.processor.current_frame]
frame = self.processor.apply_offset(frame)
# 获取直方图箱数
bins = self.hist_bins_spin.value()
# 计算直方图
hist, bin_edges = self.processor.calculate_histogram(frame)
# 显示直方图
if hist is not None:
self.histogram_canvas.plot_histogram(hist, bin_edges)
def toggle_log_scale(self):
"""切换对数坐标"""
self.histogram_canvas.log_scale = self.log_scale_check.isChecked()
self.update_histogram()
def prev_frame(self):
"""显示上一帧"""
if self.processor.frames:
new_index = max(0, self.processor.current_frame - 1)
self.frame_combo.setCurrentIndex(new_index)
self.frame_slider.setValue(new_index)
def next_frame(self):
"""显示下一帧"""
if self.processor.frames:
new_index = min(len(self.processor.frames) - 1, self.processor.current_frame + 1)
self.frame_combo.setCurrentIndex(new_index)
self.frame_slider.setValue(new_index)
def toggle_play(self):
"""切换播放状态"""
if not self.processor.frames:
return
if self.is_playing:
self.stop_play()
else:
self.start_play()
def start_play(self):
"""开始播放"""
self.is_playing = True
self.btn_play.setText("停止")
self.play_timer.start(self.play_speed)
def stop_play(self):
"""停止播放"""
self.is_playing = False
self.btn_play.setText("播放")
self.play_timer.stop()
def update_play_speed(self):
"""更新播放速度"""
self.play_speed = self.speed_spin.value()
if self.is_playing:
self.play_timer.setInterval(self.play_speed)
def update_display_range(self):
"""更新显示范围"""
self.processor.display_min = self.min_spin.value()
self.processor.display_max = self.max_spin.value()
if self.processor.frames:
self.display_frame(self.processor.current_frame)
self.update_histogram()
def update_offset(self):
"""更新整体偏移"""
self.processor.offset = self.offset_spin.value()
if self.processor.frames:
self.display_frame(self.processor.current_frame)
self.update_histogram()
def zoom_in(self):
"""放大图像"""
self.image_label.zoom_factor = min(5.0, self.image_label.zoom_factor + 0.1)
self.image_label.update_zoomed_pixmap()
self.update_frame_info()
def zoom_out(self):
"""缩小图像"""
self.image_label.zoom_factor = max(0.1, self.image_label.zoom_factor - 0.1)
self.image_label.update_zoomed_pixmap()
self.update_frame_info()
def zoom_reset(self):
"""重置缩放"""
self.image_label.zoom_factor = 1.0
self.image_label.update_zoomed_pixmap()
self.update_frame_info()
def update_frame_info(self):
"""更新帧信息显示"""
if self.processor.frames:
self.display_frame(self.processor.current_frame)
def export_data(self):
"""导出数据到文件"""
if not self.processor.frames:
return
output_path, _ = QFileDialog.getSaveFileName(
self, "保存数据", "", "文本文件 (*.txt)"
)
if output_path:
mode_map = {
"Hex": "hex",
"无符号十进制": "unsigned",
"有符号十进制": "signed"
}
mode = mode_map[self.format_combo.currentText()]
combined = self.combined_check.isChecked()
self.processor.export_data(output_path, mode, combined)
self.statusBar().showMessage(f"数据已导出到: {output_path}")
def export_image(self):
"""导出当前帧图像"""
if not self.processor.frames:
return
file_format = self.image_format_combo.currentText().lower()
filters = {
"PNG": "PNG图像 (*.png)",
"JPEG": "JPEG图像 (*.jpg *.jpeg)",
"BMP": "位图图像 (*.bmp)"
}
output_path, _ = QFileDialog.getSaveFileName(
self, "保存图像", "", filters[file_format.upper()]
)
if output_path:
# 确保文件扩展名正确
if not output_path.endswith(f".{file_format}"):
output_path += f".{file_format}"
# 获取当前帧图像
frame = self.processor.frames[self.processor.current_frame]
frame = self.processor.apply_offset(frame)
normalized = self.processor.normalize_frame(frame)
# 创建QImage
height, width = normalized.shape
qimage = QImage(width, height, QImage.Format_Grayscale8)
for y in range(height):
for x in range(width):
value = normalized[y, x]
qimage.setPixelColor(x, y, QColor(value, value, value))
# 保存图像
qimage.save(output_path)
self.statusBar().showMessage(f"图像已导出到: {output_path}")
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
```
### 主要改进点
1. **连续帧处理优化**:
```python
def load_file(self, file_path):
"""读取.dat文件并解析帧数据"""
self.frames = []
self.file_path = file_path
self.file_size = os.path.getsize(file_path)
# 计算总帧数
total_frames = self.file_size // self.frame_size
if total_frames == 0:
return
with open(file_path, 'rb') as f:
for frame_idx in range(total_frames):
# 计算当前帧位置
start_pos = frame_idx * self.frame_size
end_pos = start_pos + self.frame_size
# 移动到当前帧起始位置
f.seek(start_pos)
# 读取帧数据
frame_data = f.read(self.frame_size)
if len(frame_data) < self.frame_size:
# 不完整帧处理
frame_data += b'\x00' * (self.frame_size - len(frame_data))
# 解析16位有符号整数
fmt = '<' + 'h' * (self.frame_size // 2)
try:
frame = np.array(struct.unpack(fmt, frame_data))
frame = frame.reshape((self.frame_height, self.frame_width))
self.frames.append(frame)
except struct.error:
# 如果解析失败,跳过该帧
continue
```
- 移除了帧间隔设置,直接根据帧大小计算位置
- 使用文件大小除以帧大小计算总帧数
- 逐帧读取数据,无需间隔处理
2. **新增播放功能**:
```python
def toggle_play(self):
"""切换播放状态"""
if not self.processor.frames:
return
if self.is_playing:
self.stop_play()
else:
self.start_play()
def start_play(self):
"""开始播放"""
self.is_playing = True
self.btn_play.setText("停止")
self.play_timer.start(self.play_speed)
def stop_play(self):
"""停止播放"""
self.is_playing = False
self.btn_play.setText("播放")
self.play_timer.stop()
```
- 使用QTimer实现自动播放
- 可调节播放速度(毫秒级控制)
3. **图像导出功能**:
```python
def export_image(self):
"""导出当前帧图像"""
if not self.processor.frames:
return
file_format = self.image_format_combo.currentText().lower()
filters = {
"PNG": "PNG图像 (*.png)",
"JPEG": "JPEG图像 (*.jpg *.jpeg)",
"BMP": "位图图像 (*.bmp)"
}
# ... 保存图像代码 ...
```
- 支持导出为PNG、JPEG、BMP格式
- 自动添加文件扩展名
4. **直方图对数坐标**:
```python
def plot_histogram(self, hist, bins):
"""绘制直方图"""
self.ax.clear()
# 应用对数坐标
if self.log_scale:
hist = np.where(hist > 0, np.log10(hist + 1), 0)
ylabel = '频数 (对数)'
else:
ylabel = '频数'
# ... 绘图代码 ...
```
- 添加对数坐标选项
- 避免对零值取对数
### 文件信息显示
```python
# 计算文件信息
file_size_mb = self.processor.file_size / (1024 * 1024)
frame_count = len(self.processor.frames)
frame_size_kb = self.processor.frame_size / 1024
total_frames = self.processor.file_size // self.processor.frame_size
file_info = (f"文件大小: {file_size_mb:.2f} MB | "
f"总帧数: {total_frames} | "
f"帧大小: {frame_size_kb:.1f} KB")
self.file_info_label.setText(file_info)
```
### 使用说明
1. **文件加载**:
- 选择分辨率后打开DAT文件
- 程序自动计算帧数并加载
2. **帧浏览**:
- 使用滑块或下拉框选择帧
- 点击"播放"按钮自动播放
- 调节播放速度控制播放节奏
3. **图像处理**:
- 调整显示范围优化图像对比度
- 应用整体偏移校正数据
- 使用Ctrl+滚轮缩放图像
4. **数据导出**:
- 导出原始数据为文本文件
- 导出当前帧为PNG/JPEG/BMP图像
阅读全文
相关推荐
















