Poisson_Image-Editing

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

原图:

4f7014ca83fc4a318b6b3127bbca9b92.png1592bd6f637f444e9f97253e4e2fd6a5.png

 效果图:

29a73a6339f546f78377b0c4d829a9d2.png

 

示例2

基于GUI后端自定义框输出编辑图像结果:

6417882057c54534a141d08ea3021cef.png

 

4.代码解析

4.tests文件下

4.1.1.data.py

        这部分代码能实现文件下载的功能,通过 download() 函数,可以从给定的链接列表中下载文件。根据下载的图片可以对图片进行编辑;还有通过 square() 函数和circle() 函数对正方形图像生成和圆形图像生成的功能,使用该方法创建出的图像可以进行测试和可视化;其中代码还利用 OpenCV 库进行图像处理和操作系统交互,以及使用 NumPy 库进行科学计算和数据处理。代码通过函数和循环结构实现了模块化和可扩展的设计,使得功能的实现更加灵活和易于维护。

#!/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.fpei文件下

该包中的代码与解析如下:

6d8f1f0161434d059c34bab58a4fecd2.png

4.3 args.py

        本段代码用于解析命令行参数并提供一些选项和参数供用户在命令行中指定和操作,使用parser.add_argument()方法添加各种命令行选项,每个选项都有不同的短选项形式(如 -v)和长选项形式(如 --version),以及相应的参数类型、默认值和帮助信息。这些选项包括版本信息、后端选择、CPU 数量、CUDA 块大小、并行计算方法、源图像文件名、掩膜图像文件名、目标图像文件名、输出图像文件名、掩膜在源图像和目标图像中的位置、梯度计算方式、迭代次数等。可以根据用户的输入生成一个包含解析后参数值的argparse.Namespac对象,以便后续使用这些参数进行泊松图像编辑的相关操作。

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

        用于执行处理图像。该代码中导入所需的模块和类来解析命令行参数,通过解析命令行参数获取相关配置信息,选择合适的处理器进行图像处理,进行迭代处理并输出结果图像。

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,其中包含三个窗口:源图像窗口、目标图像窗口和结果图像窗口。它允许用户通过鼠标选择合适的区域,并通过图像处理算法生成结果图像。

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)

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/601398.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Linux][网络][TCP][四][流量控制][拥塞控制]详细讲解

目录 1.流量控制2.拥塞控制0.为什么要有拥塞控制&#xff0c;不是有流量控制么&#xff1f;1.什么是拥塞窗口&#xff1f;和发送窗口有什么关系呢&#xff1f;2.怎么知道当前网络是否出现了拥塞呢&#xff1f;3.拥塞控制有哪些算法&#xff1f;4.慢启动5.拥塞避免6.拥塞发生7.快…

【XR806开发板试用】阻塞式串口发送与接收教程

本文基于wsl2搭建的ubuntu18.04 vscode编辑器 很奇怪啊&#xff0c;找了半天居然没人发串口的教程&#xff0c;于是只能自己试一试了&#xff0c;在此发一个阻塞式的串口发送与接收的教程。并且&#xff0c;感谢.ACE彭洪权大佬在我配置环境遇到几十个报错的时候帮我远程搭建环…

校园论坛系统基于PHP的校园管理系统毕设校园好感度系统 校园文化建设系统APP小程序H5前后端源码交付支持二开,一次付款,终生使用

APP小程序H5前后端源码交付&#xff0c;支持二开&#xff0c;一次付款&#xff0c;终身使用&#xff0c;免费更新系统本身源码。 校园社交网络系统开发是一个复杂且综合性的项目&#xff0c;旨在为学生、教师和管理人员提供一个互动、分享和交流的平台。以下是一个关于校园社交…

燃料电池发电系统详解

目录 前言 组成结构 系统参数 常见问题 参考资料 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 组成结构 燃料电池发电系统&#xff0c;由多个子系统和子模块组成&#xff0c;示例如下&#xff1a; 燃料处理系统&#xff08;fuel processing system&#xf…

使用 Kubeadm 搭建个公网 k8s 集群(单控制平面集群)

前言 YY&#xff1a;国庆的时候趁着阿里云和腾讯云的轻量级服务器做促销一不小心剁了个手&#x1f60e;&#x1f622;&#xff0c;2 Cores&#xff0c;4G RAM 还是阔以的&#xff0c;既然买了&#xff0c;那不能不用呀&#x1f6a9;&#xff0c;之前一直想着搭建个 k8s 集群玩…

详解MySQL常用的数据类型

前言 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高数据库性能、确保数据完整性和准确性至关重要。本文将详细介绍MySQL中的数据类型&#xff0c;包括数值类型、字符…

[法规规划|数据概念]金融行业数据资产和安全管理系列文件解析(3)

“ 金融行业在自身数据治理和资产化建设方面一直走在前列。” 一直以来&#xff0c;金融行业由于其自身需要&#xff0c;都是国内开展信息化建设最早&#xff0c;信息化程度最高的行业。 在当今数据要素资产化的浪潮下&#xff0c;除了行业自身自身数据治理和资产化建设方面&am…

清华大模型ChatGLM3在本地Tesla P40上也运行起来了

正文共&#xff1a;999 字 14 图&#xff0c;预估阅读时间&#xff1a;1 分钟 我们之前部署了ChatGLM3&#xff08;清华大模型ChatGLM3部署初体验&#xff09;&#xff0c;但是运行体验比较差&#xff0c;主要就是因为Tesla M4的显存只有4 GB&#xff0c;无法支撑项目运行。为此…

PyCharm怎么安装Comate与使用示范

目录 简单介绍Comate 安装步骤详解 Comate使用示范详解 使用总结 简单介绍Comate Baidu Comate智能编码助手是一款基于文心大模型打造的编码辅助工具&#xff0c;具备多重优势&#xff0c;包括代码智能、应用场景丰富、创造价值高、广泛应用等。它能帮助开发者提升编码效率…

数仓开发中期:理论巩固

一、数仓以及商业智能&#xff08;Data Warehousing and Business Intelligence, DW/BI&#xff09;系统 1.1数据操作和数据获取的区别 对所有组织来说&#xff0c;信息都是其最重要的财富之一。信息几乎总是用作两个目的:操作型记录的保存和分析型决策的制定。简单来说&…

关于vs2019 c++ STL 中容器的迭代器的 -> 运算符的使用,以 list 双向链表为例

&#xff08;1&#xff09;如下的结构体 A &#xff0c;若有指针 p new A() &#xff1b;则可以使用 p->m &#xff0c; p->n 解引用运算符。 struct A { int m ; int n; } 对于 STL 中提供的迭代器&#xff0c;提供了类似于指针的功能。对迭代器也可以使用 -> 运算…

ElasticSearch知识点汇总

1、ES中的​​​​​​​倒排索引是什么。 倒排索引&#xff0c;是通过分词策略&#xff0c;形成了词和文章的映射关系表&#xff0c;这种词典映射表即为倒排索引 2、ES是如何实现master选举的。 选举过程主要包括以下几个步骤&#xff1a; 心跳检测&#xff1a; 每个节点…

docker安装elasticsearch:7.17.21

docker安装elasticsearch:7.17.21 下载对应版本的docker镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.21启动容器 docker run --name elasticsearch-test -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -t docker.elastic.…

学习通下载PDF资源

今天突然发现&#xff0c;学习通的pdf资源居然是没有下载入口的&#xff0c;这整的我想cv一下我的作业都搞不了&#xff0c;于是我一怒之下&#xff0c;怒了一下。 可以看到学习通的pdf资源是内嵌在网页的&#xff0c;阅读起来很不方便&#xff0c;虽然他内置了阅读器&#xf…

如何做好一个活动策划?

活动策划的关键要素是什么&#xff1f; 首先&#xff0c;要明确一个概念:做活动就是走钢丝&#xff0c;没有保险的高空走钢丝!因为&#xff0c;活动没有“彩排”&#xff0c;只有现场"直播”! 无论什么类型的活动&#xff0c;人数是50人还是2000人&#xff0c;也不论预算…

Linux网络编程(一) 网络基础

一、一些概念 1.1、局域网与广域网 局域网&#xff1a;局域网将一定区域内的各种计算机、外部设备和数据库连接起来形成计算机通信的私有网络。广域网&#xff1a;又称广域网、外网、公网。是连接不同地区局域网或城域网计算机通信的远程公共网络。 1.2、IP IP地址本质就是…

某制造公司屋顶分布式光伏发电案例分享--分布式光伏电力监控系统解决方案

安科瑞薛瑶瑶18701709087/17343930412 ★分布式光伏监控系统 分布式光伏监控电力系统遵循安全可靠、经济合理原则&#xff0c;满足电力系统自动化总体规划要求&#xff0c;且充分考虑光伏发电的因素&#xff0c;对分布式光伏发电、用电进行集中监控、统一调度、统一运维、满足…

苍穹外卖项目---------收获以及改进(5-6天)

①HttpClient 核心作用&#xff1a;在java编码中发送http请求 第一步&#xff1a;引入依赖 第二步&#xff1a;使用封装一个工具类 package com.sky.utils;import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.co…

向各位请教一个问题

这是菜鸟上的一道题目&#xff0c;单单拿出来问问大家&#xff0c;看看能不能解惑 &#xff0c;谢谢各位&#xff01; 题目25&#xff1a;求12!3!...20!的和 解题思路&#xff1a;这个题不知道为什么我用DEV C 5.11显示出来为0.000000&#xff0c;可能版本有问题&#xff1f;&a…

【JVM】内存结构

内存结构 Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区&#xff0c;其中有一些会随着虚拟机启动而创建&#xff0c;随着虚拟机退出而销毁。另外一些则是与线程一一对应的&#xff0c;这些与线程一一对应的数据区域会随着线程开始和结束而创建和销毁。 线程私有…
最新文章