我已经修完了《数字图像处理》这个课程,你给我做一个基于opencv和qt5的图片编辑器,这个图片编辑器要求和ps、美图秀秀之类基本功能相同就可以,一定要把《数字图像处理》这个课程的内容核心全部用上,要有图片编辑,滤镜文字编辑和贴纸,最基本层图等等这些,还有可以回到上一步,就是撤消,也能访问电脑磁盘去选照片,和可以保存图片到想要的地方,贴纸和字体留地方就等我导入就可以,必须用python,还有要明确项目结构。上面条件我没有说完,代码你要加上注释,不然我看不懂。还有功能要包含有:它将包含基础的功能,如亮度调整、对比度调整、模糊滤镜、锐化滤镜、裁剪、旋转、缩放、翻转、文字添加和贴纸添加等,要有良好的界面,用户进行使用时,调色板也就是功能放在最右边,只要想文件打开,打开导入,保存或者另保存这些就放在左上角,当然实际布局你按照主流的图片编辑来最好,我希望图片的图层是透明的,就像ps那种,图片之间可以叠加和调整透明度,好的你可以开始了
时间: 2025-08-03 13:07:07 浏览: 10
<think>由于项目需求复杂且代码量较大,我将分步骤进行:1.项目结构规划2.核心功能实现思路3.代码实现(由于篇幅限制,将展示关键代码部分,并尽量注释)项目结构:```ImageEditor/├──main.py#程序入口├──image_editor.py#主窗口类├──image_processor.py#图像处理类,包含各种图像处理算法├──layers.py#图层管理类├──utils.py#工具函数,如文件操作、历史记录管理├──resources/#资源文件夹(图标、贴图、字体等)│├──stickers/│├──fonts/│└──...└──ui/#存放QtDesigner设计的UI文件(可选)```我们将使用PyQt5和OpenCV。注意:OpenCV读取的图像是BGR格式,而PyQt需要RGB格式,因此需要转换。步骤:1.创建一个主窗口,包含菜单栏(打开、保存、另存为、退出等)、工具栏(撤销、重做等)、左侧图片显示区域、右侧功能面板(亮度、对比度、滤镜等调整控件)。2.实现图层管理,支持多层、透明度调整。3.实现历史记录功能(撤销、重做)。由于完整代码太长,这里我将分模块展示关键部分。首先,我们需要一个图像处理类(image_processor.py)来实现各种图像处理操作。这些操作应该是纯函数,便于撤销操作。```python#image_processor.pyimportcv2importnumpyasnpclassImageProcessor:@staticmethoddefadjust_brightness(image,value):"""调整亮度:paramimage:输入图像(numpyarray):paramvalue:亮度值,范围[-100,100],0表示不变:return:调整后的图像"""#将值从[-100,100]映射到[-1,1]的因子,然后乘以原图factor=value/100.0adjusted=image.astype(np.float32)+(factor*255)#截断到[0,255]adjusted=np.clip(adjusted,0,255).astype(np.uint8)returnadjusted@staticmethoddefadjust_contrast(image,value):"""调整对比度:paramimage:输入图像:paramvalue:对比度值,范围[-100,100]:return:调整后的图像"""factor=(value+100)/100.0#映射到[0,2],1表示不变mean=np.mean(image)adjusted=(image.astype(np.float32)-mean)*factor+meanadjusted=np.clip(adjusted,0,255).astype(np.uint8)returnadjusted@staticmethoddefapply_blur(image,ksize=5):"""应用均值模糊,ksize为核大小,必须是奇数"""returncv2.blur(image,(ksize,ksize))@staticmethoddefapply_sharpen(image):"""应用锐化滤镜(使用拉普拉斯算子)"""kernel=np.array([[-1,-1,-1],[-1,9,-1],[-1,-1,-1]])returncv2.filter2D(image,-1,kernel)@staticmethoddefrotate(image,angle):"""旋转图像,angle为角度,正数逆时针"""height,width=image.shape[:2]center=(width//2,height//2)matrix=cv2.getRotationMatrix2D(center,angle,1.0)returncv2.warpAffine(image,matrix,(width,height))#其他功能:裁剪、翻转、缩放等,可以添加更多```接下来,我们实现图层管理(layers.py)。这里我们采用类似PS的图层概念,每个图层包含一个图像和一个透明度。```python#layers.pyfromPyQt5.QtCoreimportQObjectfromPyQt5.QtGuiimportQImage,QPixmapclassLayer(QObject):def__init__(self,image,name="Layer",opacity=1.0,visible=True):super().__init__()self.image=image#原始图像数据(numpy数组)self.name=nameself.opacity=opacity#不透明度,0.0-1.0self.visible=visibledefset_opacity(self,value):self.opacity=max(0.0,min(1.0,value))defset_visible(self,visible):self.visible=visibledefget_display_image(self):"""返回调整透明度后的图像(仅用于显示,不影响原始数据)"""#这里我们暂时只返回原始图像,实际中需要考虑透明度叠加到图像上(但显示时由图层管理器处理叠加)returnself.image#可以添加其他属性,如混合模式等```然而,实际中我们不会在每个图层中存储混合后的图像,而是由图层管理器在需要时进行合成。因此,我们需要一个图层管理器:```python#layers.pyimportnumpyasnpclassLayerManager:def__init__(self):self.layers=[]#从底到顶self.canvas_size=(800,600)#画布大小,初始可以设定defadd_layer(self,layer,index=-1):"""添加图层,默认添加到最上层"""ifindex<0:self.layers.append(layer)else:self.layers.insert(index,layer)defremove_layer(self,index):if0<=index< len(self.layers):self.layers.pop(index)defmerge_visible_layers(self):"""合并所有可见图层,返回合并后的图像(numpy数组)"""ifnotself.layers:returnnp.zeros((self.canvas_size[1],self.canvas_size[0],3),dtype=np.uint8)#创建一个与画布大小相同的空白图像(RGBA)merged=np.zeros((self.canvas_size[1],self.canvas_size[0],4),dtype=np.float32)forlayerinself.layers:ifnotlayer.visible:continue#将图层的图像缩放到画布大小(这里简化处理,实际应该考虑位置)#注意:这里需要将图层的BGR图像转换为RGBA,并应用透明度img=layer.imageifimgisNone:continue#如果图层大小不等于画布,则调整ifimg.shape[:2]!=(self.canvas_size[1],self.canvas_size[0]):img=cv2.resize(img,(self.canvas_size[0],self.canvas_size[1]))#创建RGBA图像,透明度设为layer.opacityifimg.shape[2]==3:#BGRb,g,r=cv2.split(img)img_rgba=cv2.merge([r,g,b,np.full(img.shape[:2],layer.opacity*255,dtype=np.uint8)])elifimg.shape[2]==4:#BGRAb,g,r,a=cv2.split(img)a=a*layer.opacity#乘上不透明度img_rgba=cv2.merge([r,g,b,a])else:continue#将当前图层混合到合并图像img_float=img_rgba.astype(np.float32)/255.0merged=self._blend(merged,img_float)#将合并后的图像转换为0-255,并转换为BGRA(用于OpenCV)或RGBA(用于Qt)?#注意:在Qt中显示需要的是RGB或RGBAmerged=(merged*255).astype(np.uint8)#分离通道,因为OpenCV中我们使用的是BGRA顺序,但是合并后我们用的是RGBA,所以转换为RGB#注意:这里我们返回的是RGBA,因为后面要转成QImagereturnmerged[...,:3],merged[...,3]#返回RGB和Alpha通道@staticmethoddef_blend(bottom,top):"""将top图层混合到底部图层上,使用alpha混合。bottom:底部图像,RGBA,浮点数[0,1]top:顶部图像,RGBA,浮点数[0,1]"""#简单alpha混合:out=top_alpha*top+(1-top_alpha)*bottomalpha=top[...,3:4]blended=alpha*top+(1-alpha)*bottomreturnblended```注意:这个图层管理器是一个简化的版本,实际中可能需要考虑图层的位置、缩放等。接下来,我们创建一个主窗口类(image_editor.py),负责构建界面并连接信号。```python#image_editor.pyimportsysimportcv2importnumpyasnpfromPyQt5.QtWidgetsimport(QApplication,QMainWindow,QFileDialog,QAction,QLabel,QSlider,QHBoxLayout,QVBoxLayout,QWidget,QPushButton,QListWidget,QToolBar,QStatusBar)fromPyQt5.QtGuiimportQImage,QPixmap,QIconfromPyQt5.QtCoreimportQt,QSizefromimage_processorimportImageProcessorfromlayersimportLayer,LayerManagerclassImageEditor(QMainWindow):def__init__(self):super().__init__()self.setWindowTitle("ImageEditor")self.setGeometry(100,100,1200,800)#初始化图层管理器self.layer_manager=LayerManager()self.current_layer_index=-1#当前选中的图层索引#历史记录栈self.history_stack=[]#保存每次操作前的状态(图层列表的深拷贝?)self.future_stack=[]#用于重做#创建中心窗口,显示图像self.central_widget=QWidget()self.setCentralWidget(self.central_widget)#主布局:左右两个区域,左边显示图片,右边是功能面板main_layout=QHBoxLayout(self.central_widget)#左侧:图像显示区域self.image_label=QLabel()self.image_label.setAlignment(Qt.AlignCenter)self.image_label.setMinimumSize(800,600)self.image_label.setStyleSheet("background-color:#444;")#右侧:功能面板self.right_panel=QWidget()right_layout=QVBoxLayout(self.right_panel)right_layout.setAlignment(Qt.AlignTop)#亮度调整self.brightness_slider=QSlider(Qt.Horizontal)self.brightness_slider.setRange(-100,100)self.brightness_slider.setValue(0)self.brightness_slider.setTickInterval(10)self.brightness_slider.setTickPosition(QSlider.TicksBelow)right_layout.addWidget(QLabel("Brightness"))right_layout.addWidget(self.brightness_slider)#对比度调整self.contrast_slider=QSlider(Qt.Horizontal)self.contrast_slider.setRange(-100,100)self.contrast_slider.setValue(0)right_layout.addWidget(QLabel("Contrast"))right_layout.addWidget(self.contrast_slider)#滤镜按钮:模糊、锐化等self.blur_btn=QPushButton("Blur")self.sharpen_btn=QPushButton("Sharpen")right_layout.addWidget(self.blur_btn)right_layout.addWidget(self.sharpen_btn)#图层列表self.layer_list=QListWidget()right_layout.addWidget(QLabel("Layers"))right_layout.addWidget(self.layer_list)#添加所有控件到主布局main_layout.addWidget(self.image_label,3)#3份大小main_layout.addWidget(self.right_panel,1)#1份大小#创建菜单栏self._create_menus()#创建工具栏self._create_toolbars()#连接信号self.brightness_slider.valueChanged.connect(self.adjust_brightness)self.contrast_slider.valueChanged.connect(self.adjust_contrast)self.blur_btn.clicked.connect(self.apply_blur)self.sharpen_btn.clicked.connect(self.apply_sharpen)#初始状态self.update_image_display()def_create_menus(self):#文件菜单file_menu=self.menuBar().addMenu("File")#打开open_action=QAction("Open",self)open_action.triggered.connect(self.open_image)file_menu.addAction(open_action)#保存save_action=QAction("Save",self)save_action.triggered.connect(self.save_image)file_menu.addAction(save_action)#另存为save_as_action=QAction("SaveAs",self)save_as_action.triggered.connect(self.save_image_as)file_menu.addAction(save_as_action)#退出exit_action=QAction("Exit",self)exit_action.triggered.connect(self.close)file_menu.addAction(exit_action)#编辑菜单edit_menu=self.menuBar().addMenu("Edit")#撤销undo_action=QAction("Undo",self)undo_action.triggered.connect(self.undo)edit_menu.addAction(undo_action)#重做redo_action=QAction("Redo",self)redo_action.triggered.connect(self.redo)edit_menu.addAction(redo_action)def_create_toolbars(self):#工具栏:打开、保存、撤销、重做等toolbar=QToolBar("MainToolbar")toolbar.setIconSize(QSize(24,24))self.addToolBar(toolbar)#添加打开按钮open_icon=QIcon.fromTheme("document-open")#如果没有,可以加载自己的图标open_action=QAction(open_icon,"Open",self)open_action.triggered.connect(self.open_image)toolbar.addAction(open_action)#保存save_icon=QIcon.fromTheme("document-save")save_action=QAction(save_icon,"Save",self)save_action.triggered.connect(self.save_image)toolbar.addAction(save_action)#另存为save_as_icon=QIcon.fromTheme("document-save-as")save_as_action=QAction(save_as_icon,"SaveAs",self)save_as_action.triggered.connect(self.save_image_as)toolbar.addAction(save_as_action)#撤销undo_icon=QIcon.fromTheme("edit-undo")undo_action=QAction(undo_icon,"Undo",self)undo_action.triggered.connect(self.undo)toolbar.addAction(undo_action)#重做redo_icon=QIcon.fromTheme("edit-redo")redo_action=QAction(redo_icon,"Redo",self)redo_action.triggered.connect(self.redo)toolbar.addAction(redo_action)defopen_image(self):"""打开图片"""file_name,_=QFileDialog.getOpenFileName(self,"OpenImage","","ImageFiles(*.png*.jpg*.bmp*.tif)")iffile_name:#读取图像(使用OpenCV)image=cv2.imread(file_name)ifimageisnotNone:#创建新图层layer=Layer(image,name="Background",opacity=1.0)#添加到图层管理器self.layer_manager.add_layer(layer)self.current_layer_index=len(self.layer_manager.layers)-1#更新显示self.update_image_display()#更新图层列表self.update_layer_list()#保存历史状态self.push_history()defsave_image(self):#保存当前合并的图层图像ifnotself.layer_manager.layers:return#获取当前合并图像(RGB和Alpha)rgb,alpha=self.layer_manager.merge_visible_layers()#转换为BGR以便OpenCV保存bgr=cv2.cvtColor(rgb,cv2.COLOR_RGB2BGR)#检查是否有alpha,如果有,则保存为pngifnp.any(alpha<255):#合并为BGRAb,g,r=cv2.split(bgr)bgra=cv2.merge([b,g,r,alpha])cv2.imwrite("output.png",bgra)else:cv2.imwrite("output.jpg",bgr)defsave_image_as(self):#弹出对话框选择保存路径file_name,_=QFileDialog.getSaveFileName(self,"SaveImage","","PNG(*.png);;JPEG(*.jpg*.jpeg);;BMP(*.bmp);;TIFF(*.tif)")iffile_name:#获取合并图像ifnotself.layer_manager.layers:returnrgb,alpha=self.layer_manager.merge_visible_layers()bgr=cv2.cvtColor(rgb,cv2.COLOR_RGB2BGR)iffile_name.endswith('.png'):#保存为PNG(如果有alpha)ifnp.any(alpha<255):b,g,r=cv2.split(bgr)bgra=cv2.merge([b,g,r,alpha])cv2.imwrite(file_name,bgra)else:cv2.imwrite(file_name,bgr)else:cv2.imwrite(file_name,bgr)defupdate_image_display(self):"""将合并后的图像显示在QLabel上"""ifnotself.layer_manager.layers:self.image_label.clear()returnrgb,_=self.layer_manager.merge_visible_layers()#我们只需要RGB部分显示#将RGB图像转为QImageheight,width,channel=rgb.shapebytes_per_line=3*widthqimage=QImage(rgb.data,width,height,bytes_per_line,QImage.Format_RGB888)pixmap=QPixmap.fromImage(qimage)self.image_label.setPixmap(pixmap.scaled(self.image_label.width(),self.image_label.height(),Qt.KeepAspectRatio,Qt.SmoothTransformation))defadjust_brightness(self,value):"""调整当前图层的亮度"""ifself.current_layer_index>=0:#获取当前图层layer=self.layer_manager.layers[self.current_layer_index]#应用亮度调整adjusted=ImageProcessor.adjust_brightness(layer.image,value)#创建新图层(或者直接修改当前图层?如果直接修改当前图层,撤销操作需要保存状态)#这里我们直接修改当前图层的image(暂时)layer.image=adjustedself.update_image_display()defadjust_contrast(self,value):ifself.current_layer_index>=0:layer=self.layer_manager.layers[self.current_layer_index]adjusted=ImageProcessor.adjust_contrast(layer.image,value)layer.image=adjustedself.update_image_display()defapply_blur(self):ifself.current_layer_index>=0:layer=self.layer_manager.layers[self.current_layer_index]adjusted=ImageProcessor.apply_blur(layer.image)layer.image=adjustedself.update_image_display()defapply_sharpen(self):ifself.current_layer_index>=0:layer=self.layer_manager.layers[self.current_layer_index]adjusted=ImageProcessor.apply_sharpen(layer.image)layer.image=adjustedself.update_image_display()defupdate_layer_list(self):"""更新图层列表"""self.layer_list.clear()fori,layerinenumerate(self.layer_manager.layers):self.layer_list.addItem(f"{i}:{layer.name}")defpush_history(self):"""保存当前状态到历史记录"""#深拷贝图层管理器中的图层数据(这里简化,实际中需要深拷贝,但拷贝numpy数组量大,可以考虑只记录修改)#为了简单,我们只保存整个图层的列表(实际中应该保存图层的序列化)#注意:这里我们使用每个图层的image的拷贝(因为图像数据大,这里只存储了引用,实际需要改进)#由于时间关系,这里仅演示self.history_stack.append([(layer.name,layer.image.copy(),layer.opacity,layer.visible)forlayerinself.layer_manager.layers])#清空重做栈self.future_stack=[]defundo(self):ifself.history_stack:#当前状态压入重做栈current=[(layer.name,layer.image,layer.opacity,layer.visible)forlayerinself.layer_manager.layers]self.future_stack.append(current)#弹出历史记录栈,恢复状态state=self.history_stack.pop()self.layer_manager.layers=[]forsinstate:name,img,opacity,visible=slayer=Layer(img,name,opacity,visible)self.layer_manager.add_layer(layer)self.update_image_display()self.update_layer_list()defredo(self):ifself.future_stack:state=self.future_stack.pop()self.history_stack.append([(layer.name,layer.image,layer.opacity,layer.visible)forlayerinself.layer_manager.layers])self.layer_manager.layers=[]forsinstate:name,img,opacity,visible=slayer=Layer(img,name,opacity,visible)self.layer_manager.add_layer(layer)self.update_image_display()self.update_layer_list()#其他功能:裁剪、旋转、添加文字、添加贴纸等if__name__=='__main__':app=QApplication(sys.argv)window=ImageEditor()window.show()sys.exit(app.exec_())```注意:以上代码是一个基础框架,但还缺少很多功能,比如:1.裁剪、旋转、缩放、翻转等操作。2.添加文字(需要输入文字并渲染到图像上)。3.添加贴纸(从资源文件夹加载,并允许用户拖放)。4.图层的选择和移动(上移下移)、删除、调整透明度。5.历史记录的深度(当前的历史记录仅保存了图层的图像,但图像数据很大,应该考虑优化)。由于项目非常庞大,以上代码仅提供核心结构和部分功能。实际开发中还需要更多的细节和完善。为了满足课程要求,我们还需要实现数字图像处理中的更多功能,如边缘检测、阈值分割、形态学操作等。这些可以在`ImageProcessor`类中添加。运行主程序:```python#main.pyimportsysfromimage_editorimportImageEditorif__name__=='__main__':app=QApplication(sys.argv)window=ImageEditor()window.show()sys.exit(app.exec_())```由于代码量巨大,以上只是部分代码。在实际开发中,您还需要:-设计更友好的界面(使用QtDesigner)。-处理图像显示的缩放和平移。-优化历史记录(使用命令模式)。-添加更多的图像处理功能。由于篇幅限制,我们只能展示部分核心功能。您可以根据这个框架进行扩展。注意:由于历史记录保存了每一张图像的完整副本,内存占用会很大。在实际应用中,应该使用更高效的方法(如只保存修改的图层和操作)。最后,运行程序:```pythonmain.py```希望这能为您提供一个起点。由于完整实现一个类似于PS的软件是一个庞大的工程,这里无法面面俱到。如果您需要实现某个特定功能,可以进一步提问。§§相关问题§§:1.如何优化图层混合的性能?2.如何实现图层的移动、缩放和旋转?3.如何添加文字图层,并支持不同的字体和颜色?4.如何实现更复杂的历史记录管理(如命令模式)?</think>
下面我将为您设计一个基于OpenCV和PyQt5的图片编辑器,包含《数字图像处理》课程的核心功能和您要求的各项特性。
### 项目结构
```
ImageEditor/
├── main.py # 程序入口
├── editor_window.py # 主窗口类
├── image_processor.py # 图像处理核心类
├── layer_manager.py # 图层管理类
├── tools/ # 工具类
│ ├── text_tool.py
│ ├── sticker_tool.py
│ └── selection_tool.py
├── widgets/ # 自定义控件
│ ├── layer_widget.py
│ ├── adjustment_panel.py
│ └── history_stack.py
├── resources/ # 资源文件夹
│ ├── stickers/
│ ├── fonts/
│ └── icons/
└── utils/ # 工具函数
├── file_io.py
└── image_utils.py
```
### 核心功能实现
#### 1. 主窗口和界面布局 (editor_window.py)
```python
from PyQt5.QtWidgets import (QMainWindow, QAction, QFileDialog, QLabel,
QHBoxLayout, QVBoxLayout, QWidget, QSplitter,
QToolBar, QStatusBar, QGraphicsView, QGraphicsScene)
from PyQt5.QtGui import QImage, QPixmap, QIcon
from PyQt5.QtCore import Qt, QSize
import sys
class ImageEditorWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("DIP Image Editor")
self.setGeometry(100, 100, 1200, 800)
# 核心组件初始化
self.image_processor = ImageProcessor()
self.layer_manager = LayerManager()
# 创建UI
self.create_menus()
self.create_toolbars()
self.create_main_area()
self.create_side_panel()
# 状态栏
self.statusBar().showMessage("Ready")
def create_menus(self):
# 文件菜单
file_menu = self.menuBar().addMenu("File")
open_action = QAction(QIcon("resources/icons/open.png"), "Open", self)
open_action.triggered.connect(self.open_image)
save_action = QAction(QIcon("resources/icons/save.png"), "Save", self)
save_action.triggered.connect(self.save_image)
save_as_action = QAction("Save As", self)
save_as_action.triggered.connect(self.save_image_as)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addAction(save_as_action)
# 编辑菜单
edit_menu = self.menuBar().addMenu("Edit")
undo_action = QAction(QIcon("resources/icons/undo.png"), "Undo", self)
undo_action.triggered.connect(self.undo)
redo_action = QAction(QIcon("resources/icons/redo.png"), "Redo", self)
redo_action.triggered.connect(self.redo)
edit_menu.addAction(undo_action)
edit_menu.addAction(redo_action)
def create_toolbars(self):
# 主工具栏
self.main_toolbar = self.addToolBar("Main")
self.main_toolbar.setIconSize(QSize(24, 24))
self.main_toolbar.addAction(QAction(QIcon("resources/icons/open.png"), "Open", self))
self.main_toolbar.addAction(QAction(QIcon("resources/icons/save.png"), "Save", self))
self.main_toolbar.addSeparator()
# 编辑工具栏
self.edit_toolbar = self.addToolBar("Edit")
self.edit_toolbar.addAction(QAction(QIcon("resources/icons/undo.png"), "Undo", self))
self.edit_toolbar.addAction(QAction(QIcon("resources/icons/redo.png"), "Redo", self))
# 绘图工具栏
self.draw_toolbar = self.addToolBar("Drawing")
self.draw_toolbar.addAction(QAction(QIcon("resources/icons/text.png"), "Add Text", self))
self.draw_toolbar.addAction(QAction(QIcon("resources/icons/sticker.png"), "Add Sticker", self))
def create_main_area(self):
# 创建中央画布区域
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
# 主布局
main_layout = QHBoxLayout(self.central_widget)
# 图片显示区域
self.graphics_view = QGraphicsView()
self.scene = QGraphicsScene()
self.graphics_view.setScene(self.scene)
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignCenter)
self.scene.addWidget(self.image_label)
main_layout.addWidget(self.graphics_view, 3) # 3/4空间给图片
def create_side_panel(self):
# 右侧面板
side_panel = QWidget()
side_layout = QVBoxLayout(side_panel)
# 图层管理器
self.layer_widget = LayerWidget(self.layer_manager)
side_layout.addWidget(self.layer_widget)
# 调整面板
self.adjust_panel = AdjustmentPanel(self.image_processor)
side_layout.addWidget(self.adjust_panel)
# 添加到主布局
self.centralWidget().layout().addWidget(side_panel, 1) # 1/4空间给侧面板
def open_image(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Open Image", "",
"Images (*.png *.jpg *.bmp *.tiff)")
if file_path:
# 创建新图层
new_layer = self.layer_manager.create_layer(file_path)
self.layer_widget.add_layer_item(new_layer)
# 设置当前图层
self.layer_manager.set_active_layer(new_layer.id)
self.update_image_display()
def save_image(self):
# 保存所有图层合并后的结果
final_image = self.layer_manager.merge_visible_layers()
cv2.imwrite("output.png", final_image)
def update_image_display(self):
# 合并可见图层并显示
merged_image = self.layer_manager.merge_visible_layers()
height, width, channel = merged_image.shape
bytes_per_line = 3 * width
q_img = QImage(merged_image.data, width, height, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(q_img)
self.image_label.setPixmap(pixmap)
# 其他方法: undo, redo, save_image_as 等...
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ImageEditorWindow()
window.show()
sys.exit(app.exec_())
```
#### 2. 图像处理核心 (image_processor.py)
```python
import cv2
import numpy as np
class ImageProcessor:
def __init__(self):
self.current_image = None
def adjust_brightness(self, value):
"""调整亮度 (-100 到 100)"""
hsv = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# 亮度调整范围映射
value = int(value * 2.55) # -255 到 255
v = cv2.add(v, value)
v = np.clip(v, 0, 255)
hsv = cv2.merge((h, s, v))
self.current_image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
def adjust_contrast(self, value):
"""调整对比度 (0.01 到 3.0)"""
value = max(0.01, value)
mean = np.mean(self.current_image, axis=(0, 1))
adjusted = (self.current_image - mean) * value + mean
self.current_image = np.clip(adjusted, 0, 255).astype(np.uint8)
def apply_blur(self, kernel_size=5):
"""应用高斯模糊"""
self.current_image = cv2.GaussianBlur(self.current_image, (kernel_size, kernel_size), 0)
def apply_sharpen(self, amount=1.0):
"""应用锐化效果"""
kernel = np.array([[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1]]) * amount
self.current_image = cv2.filter2D(self.current_image, -1, kernel)
def rotate_image(self, angle):
"""旋转图像 (角度)"""
h, w = self.current_image.shape[:2]
center = (w//2, h//2)
matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
self.current_image = cv2.warpAffine(self.current_image, matrix, (w, h))
def flip_image(self, direction='horizontal'):
"""翻转图像"""
if direction == 'horizontal':
self.current_image = cv2.flip(self.current_image, 1)
else:
self.current_image = cv2.flip(self.current_image, 0)
def crop_image(self, x, y, width, height):
"""裁剪图像"""
self.current_image = self.current_image[y:y+height, x:x+width]
# 其他图像处理方法: 边缘检测, 直方图均衡化, 形态学操作等...
```
#### 3. 图层管理系统 (layer_manager.py)
```python
class Layer:
"""表示单个图层的类"""
def __init__(self, layer_id, image_data, name="Layer", opacity=1.0, visible=True):
self.id = layer_id
self.name = name
self.image_data = image_data # 原始图像数据 (numpy array)
self.opacity = opacity # 0.0 - 1.0
self.visible = visible # True/False
self.position = (0, 0) # 在画布上的位置
self.size = (0, 0) # 尺寸
self.blend_mode = "normal" # 混合模式
class LayerManager:
"""管理所有图层"""
def __init__(self):
self.layers = [] # 从底层到顶层的图层列表
self.active_layer_id = None
self.next_id = 1 # 用于生成唯一图层ID
def create_layer(self, image_data=None, name="New Layer"):
"""创建新图层"""
layer_id = self.next_id
self.next_id += 1
if image_data is None:
# 创建空白透明图层
height, width = self.layers[0].image_data.shape[:2] if self.layers else (500, 500)
image_data = np.zeros((height, width, 4), dtype=np.uint8)
new_layer = Layer(layer_id, image_data, name)
self.layers.append(new_layer)
self.active_layer_id = layer_id
return new_layer
def set_active_layer(self, layer_id):
"""设置当前活动图层"""
self.active_layer_id = layer_id
def get_active_layer(self):
"""获取当前活动图层"""
for layer in self.layers:
if layer.id == self.active_layer_id:
return layer
return None
def merge_visible_layers(self):
"""合并所有可见图层"""
if not self.layers:
return None
# 从底层开始合并
result = self.layers[0].image_data.copy()
for layer in self.layers[1:]:
if layer.visible:
# 应用混合模式和透明度
blended = self.blend_images(result, layer.image_data, layer.blend_mode)
result = self.apply_opacity(result, blended, layer.opacity)
return result
def blend_images(self, base, overlay, blend_mode):
"""应用不同的混合模式"""
# 实现正常、叠加、正片叠底等混合模式
if blend_mode == "normal":
return overlay
elif blend_mode == "multiply":
return (base * overlay) / 255
# 其他混合模式...
def apply_opacity(self, base, overlay, opacity):
"""应用透明度混合"""
return cv2.addWeighted(overlay, opacity, base, 1 - opacity, 0)
# 其他方法: 移动图层, 调整大小, 调整顺序, 分组等...
```
#### 4. 历史记录系统 (widgets/history_stack.py)
```python
class HistoryStack:
"""实现撤销/重做功能"""
def __init__(self, max_steps=20):
self.undo_stack = []
self.redo_stack = []
self.max_steps = max_steps
def push_state(self, state):
"""保存当前状态"""
if len(self.undo_stack) >= self.max_steps:
self.undo_stack.pop(0)
self.undo_stack.append(state)
self.redo_stack = [] # 清除重做栈
def undo(self):
"""撤销操作"""
if len(self.undo_stack) > 1: # 至少保留一个状态
current = self.undo_stack.pop()
self.redo_stack.append(current)
return self.undo_stack[-1] # 返回前一个状态
return None
def redo(self):
"""重做操作"""
if self.redo_stack:
next_state = self.redo_stack.pop()
self.undo_stack.append(next_state)
return next_state
return None
```
#### 5. 文字和贴纸工具 (tools/text_tool.py)
```python
class TextTool:
def __init__(self, canvas):
self.canvas = canvas
self.fonts = self.load_fonts("resources/fonts/")
def load_fonts(self, font_dir):
"""加载系统可用字体"""
# 实际实现会从目录加载字体文件
return ["Arial", "Times New Roman", "宋体", "黑体"]
def add_text(self, text, position, font_name="Arial", font_size=24, color=(0, 0, 0)):
"""在画布上添加文字"""
# 创建透明文字图层
overlay = np.zeros((self.canvas.height, self.canvas.width, 4), dtype=np.uint8)
# 使用OpenCV添加文字
font = self.fonts.get(font_name, cv2.FONT_HERSHEY_SIMPLEX)
cv2.putText(overlay, text, position, font, font_size, color, 2, cv2.LINE_AA)
# 创建新文字图层
return self.canvas.layer_manager.create_layer(overlay, f"Text: {text}")
class StickerTool:
def __init__(self, canvas):
self.canvas = canvas
self.stickers = self.load_stickers("resources/stickers/")
def load_stickers(self, sticker_dir):
"""加载贴纸资源"""
sticker_list = []
for file in os.listdir(sticker_dir):
if file.endswith(('.png', '.jpg')):
img = cv2.imread(os.path.join(sticker_dir, file), cv2.IMREAD_UNCHANGED)
sticker_list.append({
'name': os.path.splitext(file)[0],
'image': img
})
return sticker_list
def add_sticker(self, sticker_name, position):
"""在指定位置添加贴纸"""
sticker = next((s for s in self.stickers if s['name'] == sticker_name), None)
if sticker:
# 创建透明图层并放置贴纸
sticker_layer = np.zeros((self.canvas.height, self.canvas.width, 4), dtype=np.uint8)
# 将贴纸复制到指定位置
# ...实现贴纸放置逻辑...
return self.canvas.layer_manager.create_layer(sticker_layer, f"Sticker: {sticker_name}")
```
### 关键特性实现
1. **图像编辑功能**:
- 亮度/对比度调整
- 模糊/锐化滤镜
- 裁剪/旋转/翻转
- 图层透明度调整
- 混合模式支持
2. **数字图像处理核心**:
```python
# 添加在 image_processor.py 中的高级功能
def edge_detection(self, method='canny'):
"""边缘检测"""
gray = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)
if method == 'canny':
edges = cv2.Canny(gray, 100, 200)
elif method == 'sobel':
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=5)
edges = cv2.magnitude(sobelx, sobely)
return cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
def histogram_equalization(self, channel='all'):
"""直方图均衡化"""
if channel == 'all' or self.current_image.ndim == 2:
return cv2.equalizeHist(self.current_image)
else:
channels = cv2.split(self.current_image)
if channel == 'hsv':
hsv = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
v = cv2.equalizeHist(v)
return cv2.cvtColor(cv2.merge([h, s, v]), cv2.COLOR_HSV2BGR)
# 其他通道处理...
def morphological_operation(self, operation='erode', kernel_size=3):
"""形态学操作"""
kernel = np.ones((kernel_size, kernel_size), np.uint8)
if operation == 'erode':
return cv2.er
阅读全文
相关推荐



















