Fast-Poisson-Image-Editing代码介绍(一)

发布于:2024-05-14 ⋅ 阅读:(140) ⋅ 点赞:(0)

目录

1. tests文件下

1.1 data.py

2. fpei文件下 

2.1 args.py

2.2 cli.py

2.3 gui.py

2.4 Io.py

2.5 np_solver.py


代码解析

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函数) 和图像生成 ( squarecircle函数)。

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 的元组


网站公告

今日签到

点亮在社区的每一天
去签到