目录
代码解析
1. tests文件下
1.1 data.py
#!/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) # 生成一个圆形图像
这段代码主要包含两个部分:文件下载 ( download函数) 和图像生成 ( square和circle函数)。
2. fpei文件下
该包中的代码与解析如下:
2.1 args.py
用于解析命令行参数并返回一个包含解析结果的argparse.Namespace对象。
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
2.2 cli.py
用于执行处理图像。该代码中导入所需的模块和类来解析命令行参数、读取图像和创建图像处理器对象,并输出相关信息和结果。
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格式化字符串的方式打印成功写入图像文件的信息
2.3 gui.py
该代码用于实现一个简单的图像处理GUI,其中包含三个窗口:源图像窗口、目标图像窗口和结果图像窗口。它允许用户通过鼠标选择合适的区域,并通过图像处理算法生成结果图像。
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)
2.4 Io.py
提供了对图像读取和写入的封装,并通过处理确保图像数据的正确性,例如将灰度图转为 RGB 格式,删除多余的通道,以及在缺失掩码文件时创建默认的白色掩码。
import os # 导入 os模块,提供一种与操作系统交互的方式
import warnings # 导入warnings模块,提供一种处理程序执行过程中可能出现的警告消息的方法
from typing import Tuple # 导入了 Tuple类型,用于指示函数或变量应该是元组类型
import cv2 # 用于计算机视觉任务的OpenCV库
import numpy as np # 导入了 numpy模块,并为它分配一个别名 np.NumPy是Python中用于数值计算的强大库。提供了对大型多维数组和矩阵的支持,以及对这些数组进行操作的数学函数集合
def read_image(name: str) -> np.ndarray: # 该函数接受一个图像文件名,返回一个np.ndarray类型的对象
img = cv2.imread(name) # 读取图像文件,并将结果赋值给变量 img
if len(img.shape) == 2: # 如果图像的形状长度为2,则表示该图像是灰度图(单通道图像)
img = np.stack([img, img, img], axis=-1) # 将图像复制成三个相同的通道,这样就得到了一个形状为(H,W,3)的RGB图像
elif len(img.shape) == 4: # 如果图像的形状长度为 4,则表示该图像可能是视频或具有额外通道的图像
img = img[..., :-1] # 通过切片操作 img[..., :-1],从多通道图像中删除最后一个通道
#这是因为大多数情况下,图像的最后一个通道是不需要的,例如在BGR图像中,最后一个通道通常是Alpha通道
return img
# 接受一个字符串类型的参数 name(表示图像文件名)和一个 np.ndarray 类型的参数 image(表示要写入的图像)
def write_image(name: str, image: np.ndarray) -> None:
cv2.imwrite(name, image) # 图像数组image写入指定的文件名name中
def read_images( # 用于读取源图像、掩码图像和目标图像 该函数接受三个字符串类型的参数
src_name: str, # 表示源图像的文件名
mask_name: str, # 表示目标图像的文件名
tgt_name: str, # 表示目标图像的文件名
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: # 函数返回一个元组,包含了三个 np.ndarray 类型的对象,分别表示读取的源图像、掩码图像和目标图像
src, tgt = read_image(src_name), read_image(tgt_name) # 通过调用read_image函数分别读取源图像和目标图像,并将结果分别赋值给变量src和tgt
if os.path.exists(mask_name): # 检查掩码文件是否存在
mask = read_image(mask_name) # 如果存在,就使用 read_image 函数读取掩码图像
else: # 否则,产生一个警告消息
warnings.warn("No mask file found, use default setting")
mask = np.zeros_like(src) + 255 # 并创建一个与源图像大小相同、全白色的掩码图像
return src, mask, tgt # 最后,函数返回源图像、掩码图像和目标图像作为一个元组
2.5 np_solver.py
包含两个求解器类EquSolver 和 GridSolver,实现了基于 Jacobi 迭代方法的方程求解。EquSolver 主要用于处理方程组,而 GridSolver 用于处理网格上的方程组。
from typing import Tuple # 导入了 Tuple类型,用于指示函数或变量应该是元组类型
import numpy as np # 导入了 numpy模块,并为它分配一个别名 np.NumPy是Python中用于数值计算的强大库。提供了对大型多维数组和矩阵的支持,以及对这些数组进行操作的数学函数集合
class EquSolver(object): # 定义了求解器类EquSolver
"""Numpy-based Jacobi method equation solver implementation."""
def __init__(self) -> None: # 初始化函数,设置初始属性
super().__init__()
self.N = 0
# 该方法接受一个np.ndarray类型的参数mask,表示掩码图像,函数返回一个np.ndarray类型的对象,表示生成的分区图像
def partition(self, mask: np.ndarray) -> np.ndarray:
return np.cumsum((mask > 0).reshape(-1)).reshape(mask.shape)
# 该方法首先将 mask 数组中大于0的元素转换为布尔类型,并使用reshape(-1)将数组展平成一维。
# 然后,通过调用np.cumsum函数对布尔数组进行累加求和,得到每个位置的累计和。
# 最后,再使用reshape(mask.shape)将一维数组重新恢复成与掩码图像相同的形状
# 该方法用于重置求解器的状态,接受四个参数
def reset(self, N: int, A: np.ndarray, X: np.ndarray, B: np.ndarray) -> None:
"""(4 - A)X = B"""
self.N = N # 表示矩阵的大小
self.A = A # 表示矩阵A
self.B = B # 表示矩阵B
self.X = X # 表示解向量X
# 该方法用于同步求解器状态
def sync(self) -> None:
pass
# 该方法用于执行求解器的迭代步骤
# 接受一个整数类型的参数iteration,表示要执行的迭代次数
# 函数返回一个元组,包含两个 np.ndarray 类型的对象,分别表示迭代后的解向量和误差
def step(self, iteration: int) -> Tuple[np.ndarray, np.ndarray]:
for _ in range(iteration):
# X = (B + AX) / 4
self.X = ( # 根据公式 X = (B + AX) /4 更新解向量self.X
self.B + self.X[self.A[:, 0]] + self.X[self.A[:, 1]] +
self.X[self.A[:, 2]] + self.X[self.A[:, 3]]
) / 4.0
tmp = self.B + self.X[self.A[:, 0]] + self.X[self.A[:, 1]] + \
self.X[self.A[:, 2]] + self.X[self.A[:, 3]] - 4.0 * self.X
err = np.abs(tmp).sum(axis=0) # 计算 tmp 的绝对值之和作为误差 err
x = self.X.copy() # 创建一个副本x,将解向量self.X复制给它
x[x < 0] = 0
x[x > 255] = 255 # 将x约束在0到255的范围内。
return x, err # 回约束后的解向量 x 和误差 err 的元组
class GridSolver(object): #定义了求解器类GridSolver
"""Numpy-based Jacobi method grid solver implementation."""
def __init__(self) -> None: # 定义了构造函数方法,__init__方法会在创建类的对象时自动调用
super().__init__() # 调用父类的构造函数
self.N = 0
# 该方法用于重置求解器的状态,并接受四个参数:
# N(表示网格大小)、mask(掩码图像)、tgt(目标图像)和 grad(梯度图像)
def reset( # 此方法用于重置对象的状态
self, N: int, mask: np.ndarray, tgt: np.ndarray, grad: np.ndarray
) -> None: # 它需要四个参数: N(整数), mask, tgt, 和 grad(这三个都是NumPy数组)
self.N = N # 指定参数的值 N到实例变量 N对象中
self.mask = mask # 指定参数的值 mask到实例变量 mask对象中
self.bool_mask = mask.astype(bool) # 将mask转换为布尔类型,使用astype(bool)方法生成一个新的数组
self.tgt = tgt # 指定参数的值 tgt到实例变量 tgt对象中
self.grad = grad # 指定参数的值 grad到实例变量 grad对象中
def sync(self) -> None: # 此方法为空,不执行任何操作
pass # 是一个占位符方法,可以在以后实现同步
# 该方法用于执行求解器的迭代步骤,并接受一个整数类型的参数 iteration,
# 表示要执行的迭代次数。函数返回一个元组,包含两个 np.ndarray 类型的对象,分别表示更新后的目标图像和误差
def step(self, iteration: int) -> Tuple[np.ndarray, np.ndarray]:
for _ in range(iteration):
# tgt = (grad + Atgt) / 4
tgt = self.grad.copy() # 首先将梯度图像self.grad复制给临时变量tgt
# 通过对 tgt 进行索引操作,将四个方向(上、下、左、右)的目标图像 self.tgt 加到 tgt 上。
tgt[1:] += self.tgt[:-1] # 添加目标图像( self.tgt)向上偏移一个像素
tgt[:-1] += self.tgt[1:] # 将向下偏移一个像素的目标图像添加到 tgt图像
tgt[:, 1:] += self.tgt[:, :-1] # 将向左偏移一个像素的目标图像添加到 tgt图像
tgt[:, :-1] += self.tgt[:, 1:] # 这一行将向右偏移一个像素的目标图像添加到 tgt图像
# 通过对掩码图像进行索引操作,将计算得到的结果除以4.0,并将其赋值给目标图像self.tgt中对应的位置。
self.tgt[self.bool_mask] = tgt[self.bool_mask] / 4.0
# 在循环结束后,计算误差
tmp = 4 * self.tgt - self.grad # 这条线计算4倍目标图像之间的差( self.tgt)和梯度图像( self.grad),并将其赋给临时变量 tmp
tmp[1:] -= self.tgt[:-1] # 减去目标图像向上偏移一个像素
tmp[:-1] -= self.tgt[1:] # 去向下偏移一个像素的目标图像
tmp[:, 1:] -= self.tgt[:, :-1] # 减去向左偏移一个像素的目标图像
tmp[:, :-1] -= self.tgt[:, 1:] # 去向右偏移一个像素的目标图像
err = np.abs(tmp[self.bool_mask]).sum(axis=0) # 通过对掩码图像进行索引操作,计算tmp的绝对值之和作为误差
tgt = self.tgt.copy() # 创建一个副本 tgt,将目标图像 self.tgt 复制给它
tgt[tgt < 0] = 0 # 设置所有的 tgt小于0的像素为0
tgt[tgt > 255] = 255 # 将tgt约束在0到255的范围内
return tgt, err # 返回约束后的目标图像 tgt 和误差 err 的元组