1 . 算法介绍
快速泊松图像编辑(Fast Poisson Image Editing)是一种图像处理算法,用于将源图像的某个区域无缝地嵌入到目标图像中。它基于泊松方程的性质,通过求解离散化的泊松方程来实现图像的融合。该算法的核心思想是,在目标图像中构造一个与源图像区域相同大小的梯度场,并根据梯度场和源图像区域的边界条件求解离散化的泊松方程。通过求解泊松方程,可以得到一个平滑的修复结果,使得源图像区域与目标图像无缝衔接。
为了提高算法的效率,快速泊松图像编辑采用了一些优化技巧。例如,可以使用多重网格方法加速泊松方程的求解过程,以减少迭代次数。还可以利用并行计算和矩阵运算等技术,提高算法的计算速度。
快速泊松图像编辑在许多图像处理任务中具有广泛的应用,如图像修复、图像合成、图像拼接等。它能够有效地将源图像的特定内容融合到目标图像中,产生逼真的合成结果。
2.基本原理
Fast Poisson Image Editing 的核心是通过求解泊松方程来进行图像修复。该方程涉及图像的梯度和掩码信息。
修复过程中,算法通过迭代方法不断更新图像像素的值,以减小梯度的误差,达到图像平滑的效果。
2.1 PIE 算法
Fast Poisson Image Editing 采用了 PIE(Poisson Image Editing)算法,该算法使用了 Jacobi 方法对泊松方程进行求解。
PIE 算法将图像修复任务分解为多个网格,通过并行计算提高了求解效率。
2.2 程序结构
Fast Poisson Image Editing 的实现涉及多个 Python 文件,包括 process.py 和 taichi_solver.py。
process.py 中包含了图像处理器的定义,通过调用 Taichi 求解器进行图像修复。
taichi_solver.py 中包含了基于 Taichi 的 Jacobi 方法求解器的定义,提供了对泊松方程的求解和图像修复的功能。
2.3 图像处理流程
图像处理流程主要包括初始化、重置状态、执行多次迭代等步骤。
使用 PIE 算法,通过不断迭代更新图像像素值,最终得到修复后的图像。
2.4 核心类和方法
GridProcessor 类负责执行 PIE 算法中的网格处理,通过调用 Taichi 求解器进行图像修复。
EquSolver 和 GridSolver 类分别实现了基于 Taichi 的 Jacobi 方法的方程求解器和网格求解器。
2.5 Taichi 求解器
Taichi 求解器通过 Jacobi 方法对泊松方程进行求解,包含了初始化、重置状态、执行迭代等方法。
使用 Taichi 字段来存储误差、系数矩阵、常数项、未知变量等信息。
2.6 功能总结
Fast Poisson Image Editing 通过泊松方程求解,实现了图像的高效修复。
通过并行计算和优化的算法结构,提高了图像修复的速度和效果。
3.代码实现
示例1
原图:
效果图:
示例2
基于GUI后端自定义框输出编辑图像结果:
4. 代码解析
4. 1 tests文件下
4.1.1.data .py
这部分代码能实现文件下载的功能,通过 download() 函数,可以从给定的链接列表中下载文件。根据下载的图片可以对图片进行编辑;还有通过 square() 函数和circle() 函数对正方形图像生成和圆形图像生成的功能,使用该方法创建出的图像可以进行测试和可视化;其中代码还利用 OpenCV 库进行图像处理和操作系统交互,以及使用 NumPy 库进行科学计算和数据处理。代码通过函数和循环结构实现了模块化和可扩展的设计,使得功能的实现更加灵活和易于维护。
python
#!/usr/bin/env python3
import os # 提供与操作系统交互的功能
import sys # 提供与python解释器和运行时环境交互的功能
from typing import List, Tuple
import cv2 # OpenCV库的Python接口,用于图像处理和计算机视觉
import numpy as np # 导入numpy库并命名为np,用于科学计算和数据处理
# ->None表示该函数不返回任何内容
def download(links: List[Tuple[str, str]]) -> None: # 输入参数为包含元组的列表,列表中的元素都是一个包含两个字符串的元组
for link, filename in links: # 遍历列表每个元素中元组的link和filename。
if not os.path.exists(filename): # 判断filename是否存在
os.system(f"wget {link} -O {filename}") # 如果不存在,则使用os.system()函数调用系统命令来下载
# -O选项用于指定下载文件的保存路径和文件名
def square(x: int) -> None: # 该函数接受一个整数参数x
r = int((4**x)**.5 + 2) # 根据接受的x计算出正方形的边长r
img = np.zeros([r, r, 3], np.uint8) + 255 # 元素初始化为0时,像素为黑色,然后将每个元素加上255,表示为白色
cv2.imwrite(f"square{x}.png", img) # 使用OpenCV的imwrite()函数将图像保存为PNG文件。
def circle(x: int) -> None: # 接受一个整型参数x
r = int(((4**x) * 4 / np.pi)**.5 + 2) # 根据给定的x计算对于的半径
img = np.zeros([r, r, 3], np.uint8) # 创建一个大小为[r,r,3]的三维数组img用于表示图像的高度、宽度和通道数
img = cv2.circle( # 再调用OpenCV的circle()函数在图像上绘制一个圆形
img, (int(r / 2), int(r / 2)), int(r / 2), (255, 255, 255), -1
) # 指定圆心的位置,半径和颜色。参数-1表示填充整个圆形
cv2.imwrite(f"circle{x}.png", img) # 使用OpenCV的imwrite()函数将图像保存为PNG文件。
if __name__ == "__main__":
if sys.argv[-1] != "benchmark": # 判断命令行参数的最后一个元素是否为字符串"benchmark"
links = [i.split() for i in open("data1.txt").read().splitlines()] # 如果不是,则读取data1.txt文件中的内容,并将其分割成列表,再对每一行,将文件内容分割成一个字符串列表
download(links) # 将拆分后的link和filename列表作为参数进行下载
for i in range(6, 11):
square(i) # 生成一个正方形图像
circle(i) # 生成一个圆形图像
4. 2 f pei 文件下
该包中的代码与解析如下:
4.3 args .py
本段代码用于解析命令行参数并提供一些选项和参数供用户在命令行中指定和操作,使用parser.add_argument()方法添加各种命令行选项,每个选项都有不同的短选项形式(如 -v)和长选项形式(如 --version),以及相应的参数类型、默认值和帮助信息。这些选项包括版本信息、后端选择、CPU 数量、CUDA 块大小、并行计算方法、源图像文件名、掩膜图像文件名、目标图像文件名、输出图像文件名、掩膜在源图像和目标图像中的位置、梯度计算方式、迭代次数等。可以根据用户的输入生成一个包含解析后参数值的argparse.Namespac对象,以便后续使用这些参数进行泊松图像编辑的相关操作。
python
import argparse # 用于解析命令行参数
import os # 提供了与操作系统交互的功能
import fpie # 自定义的模块
from fpie.process import ALL_BACKEND, CPU_COUNT, DEFAULT_BACKEND
# 从 fpie.process 中导入的变量,可能是用于指定不同后端选项的常量或配置信息
def get_args(gen_type: str) -> argparse.Namespace: # 返回的是一个argparse.Namespace对象
parser = argparse.ArgumentParser() # 创建该对象用于解析命令行参数,将其赋值给名为parser的变量可以使用parser对象来添加命令行选项、设置默认值、指定参数类型等。
# parser.add_argument 为添加命令行选项
# help为展示对应的信息
parser.add_argument( # 添加命令行选项
"-v", "--version", action="store_true", help="show the version and exit"
) # 当用户输入"-v", "--version"时,action的参数值被设置为True,help的值为展示版本信息
parser.add_argument( # 添加命令行选项
"--check-backend", action="store_true", help="print all available backends"
) # 当用户输入"--check-backend"时,args.check_backends将被设置为True,可查看后端信息 帮助信息中提示该选项用于打印所有可用的后端信息
if gen_type == "gui" and "mpi" in ALL_BACKEND:
# gui doesn't support MPI backend
ALL_BACKEND.remove("mpi") # gui不支持mpi后端,所以需移除
parser.add_argument(
"-b", # 短选项形式
"--backend", # 长选项 都用于在命令行中指定后端选项
type=str, # 指定了该选项的值类型为字符串
choices=ALL_BACKEND, # 指定了可选的后端选项为 ALL_BACKEND 列表中的元素。
default=DEFAULT_BACKEND, # 指定了选项的默认值为DEFAULT_BACKEND
help="backend choice", # 提供对该选项的描述信息
)
parser.add_argument(
"-c", # 短选项形式
"--cpu", # 长选项形式 都用于在命令行中指定CPU数量
type=int, # 指定该选项的值类型为整数
default=CPU_COUNT, # 默认值即如果未指定该选项,则将使用默认值 CPU_COUNT
help="number of CPU used", # 获取用户在命令行中指定的CPU数量
)
parser.add_argument(
"-z", # 短选项形式
"--block-size", # 长选项形式,都用于在命令行中指定CUDA块大小
type=int, # 指定该选项的值类型为整数
default=1024, # 默认值为1024
help="cuda block size (only for equ solver)", #显示
)
parser.add_argument(
"--method", # 长选项形式
type=str, # 指定该选项的值的类型为字符串
choices=["equ", "grid"], # 两个可选值
default="equ", # 默认为equ
help="how to parallelize computation", # 显示如何并行计算
)
# 添加指定源图像的文件名的命令
parser.add_argument("-s", "--source", type=str, help="source image filename") # -s和---source都是可选命令行选项,用于指定源图像的文件名,参数类型为字符串。例如,如果用户在命令行中输入--source image.jpg,则源图像的文件名将被设置为"image.jpg"。帮助信息,提示用户该选项可用于指定源图像的文件名
if gen_type == "cli": # 如果gen_type的值为"cli"
parser.add_argument( #设置命令行选项
"-m", # 短选项形式
"--mask", # 长选项形式,都用于在命令行中指定掩膜图像文件名
type=str, # 指定该选项的值类型为字符串
help="mask image filename (default is to use the whole source image)", # 帮助信息,提示用户可以通过设置参数来指定mask图像的文件名,还提到如果用户没有指定文件名,则默认使用整个源图像进行处理
default="", # 默认为空字符串
)
# 添加指定目标图像文件名的命令
parser.add_argument("-t", "--target", type=str, help="target image filename") # 使用-t和---target选项来指定目标图像的文件名,参数类型为字符串。例如,如果用户在命令行中输入--target output.jpg,则目标图像的文件名将被设置为"output.jpg" 。帮助信息,用于提示用户该选项用于指定目标图像的文件名
# 添加指定输出图像的文件名的命令
parser.add_argument("-o", "--output", type=str, help="output image filename") # -o和---output选项用于指定输出图像的文件名,参数类型为字符串,例如,如果用户在命令行中输入--output result.jpg,则输出图像的文件名将被设置为"result.jpg。帮助信息,提示用户该选项用于指定输出图像的文件名。
if gen_type == "cli": # 如果gen_type的值为"cli"
parser.add_argument(
"-h0", type=int, help="mask position (height) on source image", default=0
) # 用于在命令行中指定掩膜在源图像中的位置(高度)
parser.add_argument(
"-w0", type=int, help="mask position (width) on source image", default=0
) # 用于在命令行中指定掩膜在源图像中的位置(宽度)
parser.add_argument(
"-h1", type=int, help="mask position (height) on target image", default=0
) # 用于在命令行中指定掩膜在目标图像中的位置(高度)
parser.add_argument(
"-w1", type=int, help="mask position (width) on target image", default=0
) # 用于在命令行中指定掩膜在目标图像中的位置(宽度)
parser.add_argument(
"-g", # 这是一个可选的命令行短选项,用于指定梯度计算
"--gradient", # 这是一个可选的命令行长选项,用于指定梯度计算
type=str, # 指定参数的类型为字符串
choices=["max", "src", "avg"], # 指定了参数的可选项,只能从该列表的元素中选择。
default="max", # 选择的参数默认为max
help="how to calculate gradient for PIE", # 提供了关于如何计算梯度的帮助信息的选项
)
parser.add_argument(
"-n", # 用于在命令行中指定迭代次数的选项
type=int, # 指定了参数的类型为整型
help="how many iteration would you perfer, the more the better", # 帮助信息,提示用户迭代次数越多,算法的性能会更好
default=5000, # 默认迭代次数为5000
)
if gen_type == "cli": # 如果gen_type的值为cli
parser.add_argument( # 用于在命令行中指定每隔多少次迭代输出结果
"-p", type=int, help="output result every P iteration", default=0 # -p选项用来指定每隔多少迭代输出结果,参数类型为整型,help选项表达的意思是,告诉用户可以用过设置参数p来指定来输出结果的频率。
)
if "mpi" in ALL_BACKEND: # 如果字符串"mpi"在后端列表中
parser.add_argument( # 添加一个命令行选项
"--mpi-sync-interval", # 命令行,用于指定mpi同步迭代间隔
type=int, # 参数类型为整型
help="MPI sync iteration interval", # 帮助信息,用于提示用户可以通过设置参数来指定mpi同步的频率
default=100,# 设置默认参数为100,即每隔100次迭代就会进行一次mpi同步操作,这样可以确保不同进程之间的数据同步和通信
) #
parser.add_argument( # 添加命令行选项
"--grid-x", type=int, help="x axis stride for grid solver", default=8
) # --grid-x 选项来指定网格求解器的 x 轴步长,参数类型为整型,帮助信息选项,提示用户可以通过设置参数来指定在网络求解器中沿着x轴步长大小 默认x轴步长为8
# 其中,sync是synchronization的缩写,表示协调多个并发操作或进程之间执行顺序和数据一致性
parser.add_argument( # 添加命令行选项
"--grid-y", type=int, help="y axis stride for grid solver", default=8
) # --grid-y 选项来指定网格求解器的 y 轴步长,参数类型为整型,帮助信息选项,用于提示用户可以通过设置参数来指定网络求解器中沿着y轴的步长大小。该步长决定了在计算过程中沿着y轴方向进行离散化间隔。 默认y轴步长为8
# axis(轴):用于描述数据在特定维度上的变化或操作。
args = parser.parse_args() # 该方法用于解析命令行参数,并将解析结果存储在变量 args 中
if args.version: # 检查 args.version 是否为真 即用户是否在命令行中指定了 -v 或 --version
print(fpie.__version__) # 输出
exit(0) # 退出程序
if args.check_backend: # 如果args对象中check_backend的属性为真
print(ALL_BACKEND) # 则打印后端所有信息
exit(0) # 退出
if not os.path.exists(args.source): # 检查源图像文件是否存在
print(f"Source image {args.source} not found.") # 输出提示
exit(1) # 用于指示程序在遇到错误或异常情况时以非零的退出码结束
if not os.path.exists(args.target): # 检查目标图像文件是否存在
print(f"Target image {args.target} not found.") # 输出提示
exit(1) # 用于指示程序在遇到错误或异常情况时以非零的退出码结束
args.mpi_sync_interval = getattr(args, "mpi_sync_interval", 0) # 获取对象 args 的属性 "mpi_sync_interval" 的值
return args
4.4 cli .py
用于执行处理图像。该代码中导入所需的模块和类来解析命令行参数,通过解析命令行参数获取相关配置信息,选择合适的处理器进行图像处理,进行迭代处理并输出结果图像。
python
import time # 导入time模块,用于处理时间相关的操作,例如等待或获取当前时间
from fpie.args import get_args # 从fpie.args模块中导入get_args类。该类用于获取命令行参数并进行解析
from fpie.io import read_images, write_image # 从fpie.io中导入了read_images和write_image类,用于读取和写入图像文件
from fpie.process import BaseProcessor, EquProcessor, GridProcessor
# 从fpie.process模块中导入BaseProcessor、EquProcessor和GridProcessor类。这些类可能用于图像处理的不同算法
def main() -> None:
args = get_args("cli") # 调用 get_args("cli") 函数来获取命令行参数,并将解析后的结果赋值给变量 args
proc: BaseProcessor
if args.method == "equ": # 如果 args.method 的值是 "equ",则使用 EquProcessor 类来实例化
proc = EquProcessor( # 将实例化后的处理器赋值给变量 proc
# 传递相应的参数如下:
args.gradient, # 命令行参数中指定的梯度值,用于均衡化处理
args.backend, # 命令行参数中指定的后端值,用于选择特定的处理后端
args.cpu, # 命令行参数中指定的CPU使用的值,用于控制处理器在CPU上的运行
args.mpi_sync_interval, # 命令行参数值中指定的mpi同步间隔值,用于在分布式环境中控制mpi进程之间的同步操作
args.block_size, # 命令行参数中指定的块大小值,用于划分图像处理
)
else: # 如果 args.method 的值不是 "equ",则使用 GridProcessor 类来实例化
# 并传递相应的参数
proc = GridProcessor(
args.gradient, # 命令行参数中指定的梯度值,用于均衡化处理
args.backend, # 命令行参数中指定的后端值,用于选择特定的处理后端
args.cpu, # 命令行参数中指定的CPU使用的值,用于控制处理器在CPU上的运行
args.mpi_sync_interval, # 命令行参数中指定的MPI同步间隔值,用于在分布式环境中控制MPI进程之间的同步操作
args.block_size, # # 命令行参数中指定的块大小值,用于划分图像处理
args.grid_x, # 命令行参数中指定的网格求解器的x轴步长值,用于在网格处理器中控制沿x轴方向的离散化间隔
args.grid_y, # 命令行参数中指定的网格求解器的y轴步长值,用于在网格处理器中控制沿y轴方向的离散化间隔
)
if proc.root: # 检查当前处理器是否为根节点
print(
f"Successfully initialize PIE {args.method} solver " # 打印初始化信息
f"with {args.backend} backend" # 是一个使用了f-string的字符串格式化表达式。在这个表达式中,{args.backend}会被替换为args.backend变量的值
)
# 调用 proc.reset() 方法对处理器进行重置,传递源图像、掩膜图像、目标图像以及感兴趣区域的坐标范围参数
src, mask, tgt = read_images(args.source, args.mask, args.target) # 调用read_images函数,从指定的源图像、遮罩图像和目标图像文件中读取图像数据,并将其分别赋值给变量src、mask和tgt
n = proc.reset(src, mask, tgt, (args.h0, args.w0), (args.h1, args.w1)) # 调用处理器对象proc的reset方法,传递源图像、遮罩图像、目标图像以及感兴趣区域的起始坐标和结束坐标作为参数
print(f"# of vars: {n}") # 使用f-string格式化字符串的方式打印出变量n的值,即处理器中的变量数量。
proc.sync() # 调用处理器对象proc的sync方法,用于进行同步操作
if proc.root: # 检查当前处理器是否为根节点
result = tgt # 初始化结果变量 result 为目标图像(tgt)
t = time.time() # 使用 time.time() 记录当前时间,并将其赋值给变量 t,以便计算总共花费的时间
if args.p == 0: # 如果参数args.p为0
args.p = args.n # ,将 args.p 的值设置为 args.n 的值,以确保每次迭代处理的默认步长等于总迭代次数 args.n
for i in range(0, args.n, args.p): # 该循环用于进行图像处理的迭代步骤
if proc.root: # 如果是根节点
result, err = proc.step(args.p) # type: ignore
print(f"Iter {i + args.p}, abs error {err}") # 调用处理器对象proc的step方法,传递步长args.p作为参数。该方法可能执行一次迭代的图像处理,并返回处理后的结果和误差
if i + args.p < args.n: # 检查是否还有剩余的迭代步骤
write_image(f"iter{i + args.p:05d}.png", result) # 将当前迭代的结果图像result写入文件,文件名以迭代次数命名
else: # 如果当前处理器不是根节点
proc.step(args.p) # 调用处理器对象proc的step方法,传递步长args.p作为参数
if proc.root: # 如果是根节点
dt = time.time() - t # 计算总共花费的时间,即当前时间减去起始时间,得到时间差,并将其赋值给变量dt
print(f"Time elapsed: {dt:.4f}s") # 使用f-string格式化字符串的方式打印总共花费的时间,保留4位小数
write_image(args.output, result) # 将最终结果图像result写入文件,文件名由参数args.output指定
print(f"Successfully write image to {args.output}") # 使用f-string格式化字符串的方式打印成功写入图像文件的信息
4.5 gui.py
该代码用于实现一个简单的图像处理GUI,其中包含三个窗口:源图像窗口、目标图像窗口和结果图像窗口。它允许用户通过鼠标选择合适的区域,并通过图像处理算法生成结果图像。
python
import time # 导入了Python标准库中的time模块,用于处理时间相关的操作,例如等待或获取当前时间
from typing import Any # 从typing模块中导入了Any类型,用于在类型注解中表示任意类型
import cv2 # 导入了OpenCV库,用于图像处理和计算机视觉任务
import numpy as np # 导入了NumPy库,用于高性能的数值计算和数组操作
from fpie.cli import get_args # 从fpie.cli模块中导入了get_args类,用于获取命令行参数并进行解析
from fpie.io import read_image, write_image # 从fpie.io模块中导入了read_image和write_image类,用于读取和写入图像文件
from fpie.process import BaseProcessor, EquProcessor, GridProcessor
# 从fpie.process模块中导入了BaseProcessor、EquProcessor和GridProcessor类。
# 这些类用于图像处理的不同算法
class GUI(object):
"""A simple GUI implementation.
3 windows:
1. src with rect bbox;
2. tgt with single point;
3. result with fix rate refresh.
"""
'''
以下代码实现了一个简单的图像显示和交互界面,允许用户在源图像和目标图像上进行操作,
并在退出时保存结果图像
'''
# 一个类的构造函数__init__(),proc是一个实现BaseProcessor接口的处理器对象。
# src表示源图像的路径,tgt表示目标图像的路径,out表示输出图像的路径,n表示迭代次数
def __init__(self, proc: BaseProcessor, src: str, tgt: str, out: str, n: int):
super().__init__() # 调用父类的构造函数,确保正确初始化继承自父类的属性
self.xt, self.yt = 0, 0 # 初始化图像的坐标
self.src = read_image(src) # 从指定路径读取源图像,并将其赋值给self.src属性
self.tgt = read_image(tgt) # 从指定路径读取目标图像,并将其赋值给self.tgt属性。
self.x0, self.y0 = 0, 0 # 初始化图像坐标
# self.src.shape 返回一个元组,包含了图像的形状信息即图像的高度、宽度和通道数(对于彩色图像)
# 使用切片操作 [:2]即只保留前两个元素,即高度和宽度
self.y1, self.x1 = self.src.shape[:2] # 度赋值给 self.y1,将宽度赋值给 self.x1
self.out = out # 将输出路径赋值给self.out属性
self.n = n # 将迭代次数赋值给self.n属性
self.proc = proc # 将处理器对象赋值给self.proc属性
cv2.namedWindow("source") # 创建名为"source"的窗口
cv2.setMouseCallback("source", self.source_callback) # 设置鼠标回调函数
cv2.namedWindow("target") # 创建名为"target"的窗口
cv2.setMouseCallback("target", self.target_callback) # 设置鼠标回调函数
# 回调函数是在用户与窗口中的图像进行交互时被调用的函数
# 分别将源图像 (self.src)、目标图像 (self.tgt)
# 和结果图像 (self.tgt) 复制给 self.gui_src、self.gui_tgt 和 self.gui_out,以便在 GUI 中显示
self.gui_src = self.src.copy() # 创建了一个源图像的副本,并将其赋值给self.gui_src属性
self.gui_tgt = self.tgt.copy() # 创建了一个目标图像的副本,并将其赋值给self.gui_tgt属性
self.gui_out = self.tgt.copy() # 创建了一个目标图像的副本,并将其赋值给self.gui_out属性
self.on_source = False # 初始化了一个布尔值变量 self.on_source,表示当前是否在源图像上操作
while True: # 不断调用 cv2.imshow 显示源图像、目标图像和结果图像的窗口
cv2.imshow("source", self.gui_src) # 将名为"source"的窗口打开,并在该窗口中显示self.gui_src属性所代表的源图像
cv2.imshow("target", self.gui_tgt) # 将名为"target"的窗口打开,并在该窗口中显示self.gui_tgt属性所代表的目标图像
cv2.imshow("result", self.gui_out) # 将名为"result"的窗口打开,并在该窗口中显示self.gui_out属性所代表的输出图像
key = cv2.waitKey(30) & 0xFF # 等待按键输入,并将按键的 ASCII 码存储在变量 key 中
if key == 27: # 如果按下 ESC 键(ASCII 码为 27)
break # 则跳出循环,停止显示图像
write_image(self.out, self.gui_out) # 将结果图像保存到指定的输出路径
cv2.destroyAllWindows() # 关闭所有窗口
'''
这段代码的作用是在源图像窗口中实现了鼠标交互操作,
允许用户通过拖动鼠标选择一个矩形区域,并将该选择框的坐标保存在相应的属性中。
'''
def source_callback(
# 该回调函数接受几个参数:event(表示事件类型),x 和 y(表示鼠标点击位置的坐标)
# flags(表示鼠标事件的附加标志),以及 param(用户定义的额外参数)
self, event: int, x: int, y: int, flags: int, param: Any
) -> None:
if event == cv2.EVENT_LBUTTONDOWN: # 如果点击鼠标左键
self.on_source = True # 则标志为True
self.x0, self.y0 = x, y # 并记录鼠标按下时的坐标
elif event == cv2.EVENT_MOUSEMOVE: # 如果拖动鼠标
if self.on_source: # 并且鼠标左键已按下
self.gui_src = self.src.copy() # 则复制源图像到self.gui_src
cv2.rectangle( # 并在self.gui_src 上绘制一个矩形框
self.gui_src, (self.x0, self.y0), (x, y), (255, 255, 255), 1
) # 框选从(x0, y0)到当前鼠标位置(x, y)的区域并以白色表示
elif event == cv2.EVENT_LBUTTONUP: # 如果释放鼠标左键
self.on_source = False # 将标志设置为False
self.x1, self.y1 = x, y # 记录下鼠标释放时的坐标
self.gui_src = self.src.copy() # 复制源图像到self.gui_src
cv2.rectangle(
self.gui_src, (self.x0, self.y0), (x, y), (255, 255, 255), 1
) # 框选从(x0, y0)到当前鼠标位置(x, y)的区域并以白色表示
# 确保左上角坐标为 (self.x0, self.y0),右下角坐标为 (self.x1, self.y1)
self.x0, self.x1 = min(self.x0, self.x1), max(self.x0, self.x1) # 通过取最小值和最大值来规范化选择框的坐标
self.y0, self.y1 = min(self.y0, self.y1), max(self.y0, self.y1) # 通过取最小值和最大值来规范化选择框的坐标
'''
这段代码的作用是在目标图像窗口中实现了鼠标交互操作
'''
def target_callback( # 用于处理目标图像的鼠标事件
self, event: int, x: int, y: int, flags: int, param: Any
) -> None:
if event == cv2.EVENT_LBUTTONDOWN: # 当按下鼠标左键时
self.gui_tgt = self.tgt.copy() # 复制目标图像 self.tgt 到 self.gui_tgt
mask_x = min(self.x1 - self.x0, self.tgt.shape[1] - x) # 选择框在x方向上的宽度,确保选择框不会超出目标图像的宽度范围
mask_y = min(self.y1 - self.y0, self.tgt.shape[0] - y) # 选择框在 y 方向上的高度,确保选择框不会超出目标图像的高度范围
cv2.rectangle( # 在 self.gui_tgt 上绘制一个矩形框
self.gui_tgt,
(x, y),
(x + mask_x, y + mask_y), # 从 (x, y) 开始,到 (x + mask_x, y + mask_y) 结束
(255, 255, 255), # 以白色边框显示
1,
)
mask = np.zeros([mask_y, mask_x], np.uint8) + 255 # 创建一个大小为 (mask_y, mask_x) 的二值化掩码图像 mask,并将所有像素值设置为 255
t = time.time() # 记录当前时间 t
# 然后调用处理器对象的 reset 方法,将源图像、掩码、目标图像以及选择框的坐标作为参数传入
self.proc.reset(self.src, mask, self.tgt, (self.y0, self.x0), (y, x))
# 调用处理器的 step 方法进行指定次数的迭代处理,并将结果保存到 self.gui_out 中。同时,将返回的误差值赋值给变量 err
self.gui_out, err = self.proc.step(self.n) # type: ignore
t = time.time() - t # 计算处理时间 t
print( # 打印相关信息,如经过的时间、掩码大小、误差值以及一些参数
f"Time elapsed: {t:.4f}s, mask size {mask.shape}, abs Error: {err}\t"
f"Args: -n {self.n} -h0 {self.y0} -w0 {self.x0} -h1 {y} -w1 {x}"
)
def main() -> None:
args = get_args("gui") # 调用get_args("gui")获取命令行参数,并将返回值赋给 args 变量
proc: BaseProcessor # 声明一个类型为BaseProcessor的变量proc,用于存储处理器对象
if args.method == "equ": # 如果方法是 "equ"
proc = EquProcessor( # 创建一个 EquProcessor 对象,传递相应的参数
args.gradient, # 命令行参数中指定的梯度值,用于均衡化处理
args.backend, # 命令行参数中指定的后端值,用于选择特定的处理后端
args.cpu, # 命令行参数中指定的CPU使用值,用于控制处理器在CPU上的运行
args.mpi_sync_interval, # 命令行参数中指定的MPI同步间隔值,用于在分布式环境中控制MPI进程之间的同步操作
args.block_size, # 命令行参数中指定的块大小值,用于划分图像处理的块
)
else: # 否则,创建一个 GridProcessor 对象,传递相应的参数
proc = GridProcessor(
args.gradient, # 命令行参数中指定的梯度值,用于均衡化处理
args.backend, # 命令行参数中指定的后端值,用于选择特定的处理后端
args.cpu, # 命令行参数中指定的CPU使用值,用于控制处理器在CPU上的运行
args.mpi_sync_interval, # 命令行参数中指定的MPI同步间隔值,用于在分布式环境中控制MPI进程之间的同步操作
args.block_size, # 命令行参数中指定的块大小值,用于划分图像处理的块
args.grid_x, # 是命令行参数中指定的网格求解器的x轴步长值,用于在网格处理器中控制沿x轴方向的离散化间隔
args.grid_y, # 命令行参数中指定的网格求解器的y轴步长值,用于在网格处理器中控制沿y轴方向的离散化间隔
)
print( # 打印一条成功初始化处理器的消息,显示所选方法和后端
f"Successfully initialize PIE {args.method} solver "
f"with {args.backend} backend" # 使用f-string格式化字符串的方式,将方法和后端的值插入到字符串中
)
# 创建一个GUI对象,传递处理器对象、源图像路径、目标图像路径、输出路径和迭代次数作为参数。
# 这样就开始执行图像编辑的 GUI 界面
GUI(proc, args.source, args.target, args.output, args.n)