修补图像


114

在一种流行的图像编辑软件还有一个特点,即补丁(在图像处理中使用的术语被补绘为@mınxomaτ指出。)中的图像的选择区域,基于所述信息以外该补丁。考虑到它只是一个程序,它做得很好。作为人类,您有时会发现有些问题,但是如果您挤眼睛或只是瞥了一眼,该补丁似乎可以很好地填补空白

流行的图像编辑软件的示例

挑战

给定图像和用于指定图像矩形区域的遮罩(也可以作为图像或其他任何首选格式)进行修补,您的程序应尝试用修补程序填充指定区域,该修补程序试图与其余部分融合图片。程序无法使用指定区域内的原始图像信息。

您可以假设补丁始终至少与侧面的宽度相等,并且距图像的顶部和底部的高度至少相等。这意味着补丁的最大面积是整个图像的1/9。

请简要说明您的算法如何工作。

表决

要求选民判断算法的性能如何,并进行相应的投票。

有关如何判断的一些建议:(再次感谢@mınxomaτ提供更多标准。)

  • 如果您起眼睛,画面看起来还好吗?
  • 您能准确说出补丁在哪里吗?
  • 图像背景和周围区域的结构和纹理的延续程度如何?
  • 编辑区域包含多少个杂散伪彩色像素?
  • 该区域中是否存在似乎不属于该区域的颜色均匀的斑点/块?
  • 与图像的其余部分相比,编辑区域是否有剧烈的色彩/对比度或亮度偏移?

有效性标准

为了使提交有效,输出图像必须与指定区域之外的输入图像完全匹配。

测试用例

在源图像的左侧,在右侧的对应蒙版:


1
我们可以接受掩码输入作为文本参数(例如inpaint.exe left top width height img.jpg)吗?
mınxomaτ

1
当然,输入/输出格式并不是那么重要,因为这是一场流行竞赛,其中算法的性能首先很重要。
缺陷

24
这是一个非常实际的挑战。结果可能会好于GIMP和其他开源图像编辑软件中使用的现有算法。财富,名望和荣耀可能属于您!
Sparr

6
@Sparr和最后丑陋的水印可以从下载的媒体中删除;)
Andras Deak

2
内建函数完全可以,但是我怀疑它们会非常流行。
瑕疵的2016年

Answers:


142

AutoIt的,VB

介绍

这是A. Criminisi,P。Perez(剑桥Microsoft Research Ltd.)和K.Toyama(Microsoft)[X]开发的基于示例的Inpainting算法去除对象的实现。该算法针对高信息图像(和视频帧),旨在在结构重建和有机重建之间取得平衡。该答案的段落包含原始论文的全文引文(由于不再正式可用),以使该答案更加独立。

算法

目标:用看起来合理的背景替换选定的(蒙版)区域(最好是在视觉上分离的前景对象)。

在以前的工作中,几位研究人员已经将纹理合成视为一种用“纯”纹理(具有中等随机性的重复二维纹理图案)填充大图像区域的方法。这是基于大量纹理合成研究的结果,该研究试图无限复制纹理,给定一小部分纯纹理[1] [8] [9] [10] [11] [12] [14] [15] [16] [19] [22]

这些技术在复制一致的纹理方面非常有效,但它们很难在现实世界的照片中填补空白,这些场景通常由线性结构和复合纹理组成–多个纹理在空间上相互作用[23]。主要问题是图像区域之间的边界是不同纹理之间相互影响的复杂产物。与纯纹理的二维性质相反,这些边界形成了可以被视为更一维或线性的图像结构。

图像修补技术是通过扩散将线性结构(在修补文献中称为等渗线)传播到目标区域中来填充图像中的孔。它们受到物理热流偏微分方程的启发,并作为还原算法令人信服地工作。它们的缺点是扩散过程会引起一些模糊,这很明显。

图。 2

要填充的区域(即目标区域)用Ω表示,其轮廓表示为δ。轮廓随着算法的进行而向内演化,因此我们也将其称为“填充前沿”。在整个算法中保持固定的源区域Φ提供了填充过程中使用的样本。现在,我们将重点放在算法的一次迭代上,以展示如何通过基于示例的综合来适当地处理结构和纹理。假设要填充以点p为中心的正方形模板squarep∈f(图2b)。来自源区域的最佳匹配样本来自块Ψqˆ∈Φ,这与已经填充Ψp的那些部分最为相似。在图的示例中。从图2b可以看出,如果Ψp位于图像边缘的连续线上,最可能的最佳匹配将位于相同(或相似颜色)的边缘(例如,图2c中的“ q”和“ q”)。向内传播等位线所需的一切只是从最匹配的源补丁(图2d)进行模式的简单转移。请注意,等渗线方向会自动保留。在图中,尽管原始边缘不垂直于目标轮廓δ1,但传播的结构仍保持与源区相同的取向。

实现和算法细节

此实现的功能封装在ActiveX COM DLL中,该DLL作为二进制文件从主机程序中删除,然后通过IID调用Inpainter即时调用。在这种特定情况下,API用VisualBasic编写,可以从任何启用COM的语言中调用。代码的以下部分删除了二进制文件:

Func deflate($e=DllStructCreate,$f=@ScriptDir&"\inpaint.dll")
    If FileExists($f) Then Return
    !! BINARY CODE OMITTED FOR SIZE REASONS !!
    $a=$e("byte a[13015]")
    DllCall("Crypt32.dll","bool","CryptStringToBinaryA","str",$_,"int",0,"int",1,"struct*",$a,"int*",13015,"ptr",0,"ptr",0)
    $_=$a.a
    $b=$e('byte a[13015]')
    $b.a=$_
    $c=$e("byte a[14848]")
    DllCall("ntdll.dll","int","RtlDecompressBuffer","int",2,"struct*",$c,"int",14848,"struct*",$b,"int",13015,"int*",0)
    $d=FileOpen(@ScriptDir&"\inpaint.dll",18)
    FileWrite($d,Binary($c.a))
    FileClose($d)
EndFunc

稍后使用CLSID和IID实例化该库:

Local $hInpaintLib = DllOpen("inpaint.dll")
Local $oInpaintLib = ObjCreate("{3D0C8F8D-D246-41D6-BC18-3CF18F283429}", "{2B0D9752-15E8-4B52-9569-F64A0B12FFC5}", $hInpaintLib)

该库接受GDIOBJECT句柄,特别是任何GDI / +位图(文件,流等)的DIBSection。加载指定的图像文件,并将其绘制到Scan0根据输入图像尺寸构造的空位图上。

此实现的输入文件是包含遮罩图像数据的任何GDI / +兼容文件格式。的掩模(一个或多个)是在输入图像中的一个或多个均匀的着色区域。用户为遮罩提供RGB颜色值,只有具有该颜色值的像素才匹配。默认的遮罩颜色为绿色(0、255、0)。所有被遮罩的区域一起代表要删除和填充的目标区域。源区域Φ定义为整个图像减去目标区域(Φ= I−Ω)。

接下来,与所有基于示例的纹理合成[10]一样,必须指定模板窗口Ψ的大小(又称“ 扫描半径 ”)。此实现提供的默认窗口大小为6²像素,但实际上要求用户将其设置为略大于源区域中最大的可区分纹理元素或“纹理”。对原始算法的另一种修改是用户可定义的“ 块大小 ”,它确定了要用新的均匀颜色替换的像素区域。这会增加速度并降低质量。块大小大于1px的块应该用于极其均匀的区域(水,沙子,毛皮等),但是Ψ应当保持在最大值。.5倍的块大小(根据掩码,这是不可能的)。

为了不使算法停滞在1bit的图像上,每次接收到少于5种颜色的图像时,窗口大小都会放大10倍。

一旦确定了这些参数,其余的区域填充过程将完全自动进行。在我们的算法中,每个像素都保留一个颜色值(如果未填充像素,则为“空”)和一个置信度值,该值反映了我们对像素值的置信度,并且在填充像素后将冻结该置信度。在算法过程中,沿填充前沿的贴片还会被赋予一个临时优先级值,该值确定填充它们的顺序。然后,我们的算法重复以下三个步骤,直到所有像素都被填满。

步骤1:计算补丁优先级

填充顺序对于非参数纹理合成至关重要[1] [6] [10] [13]。到目前为止,默认的最喜欢的方法是“洋葱皮”方法,其中目标区域是从外向内在同心层中合成的。我们的算法通过最佳优先填充算法执行此任务,该算法完全取决于分配给填充前沿每个补片的优先级值。优先级计算偏向于那些在强边缘连续且被高置信度像素包围的斑块,这些斑块是边界,由值-2标记。以下代码重新计算优先级:

For j = m_top To m_bottom: Y = j * m_width: For i = m_left To m_right
    If m_mark(Y + i) = -2 Then m_pri(Y + i) = ComputeConfidence(i, j) * ComputeData(i, j)
Next i: Next j

给定一个以点p为中心的某个点pp对于某些p∈δΩ(见图3),其优先级P(p)定义为计算出的置信度(ComputeConfidenceC(p))与数据项ComputeData,或D(p)),其中

,在哪里

|Ψp| 是p的面积,α是归一化因子(例如,对于典型的灰度图像,α= 255),np是与点p处的正面δΩ正交的单位矢量。为每个边界补丁计算优先级,并为目标区域边界上的每个像素提供不同的补丁。

实施为

Private Function ComputeConfidence(ByVal i As Long, ByVal j As Long) As Double
    Dim confidence As Double
    Dim X, Y As Long

    For Y = IIf(j - Winsize > 0, j - Winsize, 0) To IIf(j + Winsize < m_height - 1, j + Winsize, m_height - 1): For X = IIf(i - Winsize > 0, i - Winsize, 0) To IIf(i + Winsize < m_width - 1, i + Winsize, m_width - 1)
        confidence = confidence + m_confid(Y * m_width + X)
    Next X: Next Y

    ComputeConfidence = confidence / ((Winsize * 2 + 1) * (Winsize * 2 + 1))
End Function

Private Function ComputeData(ByVal i As Long, ByVal j As Long) As Double
    Dim grad As CPOINT
    Dim temp As CPOINT
    Dim grad_T As CPOINT
    Dim result As Double
    Dim magnitude As Double
    Dim max As Double
    Dim X As Long
    Dim Y As Long
    Dim nn As CPOINT
    Dim Found As Boolean
    Dim Count, num As Long
    Dim neighbor_x(8) As Long
    Dim neighbor_y(8) As Long
    Dim record(8) As Long
    Dim n_x As Long
    Dim n_y As Long
    Dim tempL As Long
    Dim square As Double

    For Y = IIf(j - Winsize > 0, j - Winsize, 0) To IIf(j + Winsize < m_height - 1, j + Winsize, m_height - 1): For X = IIf(i - Winsize > 0, i - Winsize, 0) To IIf(i + Winsize < m_width - 1, i + Winsize, m_width - 1)
        If m_mark(Y * m_width + X) >= 0 Then
            Found = False
            Found = m_mark(Y * m_width + X + 1) < 0 Or m_mark(Y * m_width + X - 1) < 0 Or m_mark((Y + 1) * m_width + X) < 0 Or m_mark((Y - 1) * m_width + X) < 0
            If Found = False Then
                temp.X = IIf(X = 0, m_gray(Y * m_width + X + 1) - m_gray(Y * m_width + X), IIf(X = m_width - 1, m_gray(Y * m_width + X) - m_gray(Y * m_width + X - 1), (m_gray(Y * m_width + X + 1) - m_gray(Y * m_width + X - 1)) / 2#))
                temp.Y = IIf(Y = 0, m_gray((Y + 1) * m_width + X) - m_gray(Y * m_width + X), IIf(Y = m_height - 1, m_gray(Y * m_width + X) - m_gray((Y - 1) * m_width + X), (m_gray((Y + 1) * m_width + X) - m_gray((Y - 1) * m_width + X)) / 2#))
                magnitude = temp.X ^ 2 + temp.Y ^ 2
                If magnitude > max Then
                    grad.X = temp.X
                    grad.Y = temp.Y
                    max = magnitude
                End If
            End If
        End If
    Next X: Next Y

    grad_T.X = grad.Y
    grad_T.Y = -grad.X

    For Y = IIf(j - 1 > 0, j - 1, 0) To IIf(j + 1 < m_height - 1, j + 1, m_height - 1): For X = IIf(i - 1 > 0, i - 1, 0) To IIf(i + 1 < m_width - 1, i + 1, m_width - 1): Count = Count + 1
        If X <> i Or Y <> j Then
            If m_mark(Y * m_width + X) = -2 Then
                num = num + 1
                neighbor_x(num) = X
                neighbor_y(num) = Y
                record(num) = Count
            End If
        End If
    Next X: Next Y

    If num = 0 Or num = 1 Then
        ComputeData = Abs((0.6 * grad_T.X + 0.8 * grad_T.Y) / 255)
    Else
        n_x = neighbor_y(2) - neighbor_y(1)
        n_y = neighbor_x(2) - neighbor_x(1)
        square = CDbl(n_x ^ 2 + n_y ^ 2) ^ 0.5
        ComputeData = Abs((IIf(n_x = 0, 0, n_x / square) * grad_T.X + IIf(n_y = 0, 0, n_y / square) * grad_T.Y) / 255)
    End If
End Function

置信项C(p)可以被认为是围绕像素p的可靠信息量的量度。目的是首先填充那些已经填充了更多像素的色块,并优先考虑较早填充(或不属于目标区域的像素)的像素。

这会自动沿填充前端合并对某些形状的偏好。例如,包含目标区域的角和细卷须的色块将被首先填充,因为它们被原始图像中的更多像素包围。这些补丁程序提供了更可靠的信息来进行匹配。相反,突出到目标区域的已填充像素“半岛”尖端的补丁将被搁置一旁,直到更多周围像素被填充为止。在粗糙级别上,(1)的项C(p)大约为强制执行所需的同心填充顺序。

随着填充的进行,目标区域外层的像素将趋向于具有更大的置信度值,因此需要更早地填充。目标区域中心的像素将具有较小的置信度值。数据项D(p)是每次迭代中等渗线撞击前部δΩ的强度的函数。该术语提高了等渗线“流入”的贴片的优先级。这个因素在我们的算法中至关重要,因为它鼓励首先合成线性结构,然后安全地传播到目标区域中。折线趋于连接,从而实现了视觉心理学的“连接原理” [7] [17]

填充顺序取决于图像属性,从而导致有机合成过程,消除了“结构破裂”伪影的风险,并且还减少了块状伪影,而无需进行昂贵的补丁切割步骤[9]或引起模糊的融合步骤[19] ]

步骤2:传播纹理和结构信息

一旦计算了填充前沿(边界)上的所有优先级,便找到具有最高优先级的补丁Ψpˆ。然后,我们用从源区域Φ提取的数据填充它。我们通过直接采样源区域来传播图像纹理。类似于[10],我们在源区域中搜索与Ψpˆ最相似的补丁。正式地,

,在哪里

两个通用色块Ψa和Ψb之间的距离d(Ψa,Ψb)简单定义为两个色块中已经填充的像素的平方差之和(SSD)。在此步骤中,无需进行进一步的分析或处理(尤其是不模糊)。此计算在主循环循环中运行,并实现如下:

获得最高优先级:

For j = m_top To m_bottom: Jidx = j * m_width: For i = m_left To m_right
    If m_mark(Jidx + i) = -2 And m_pri(Jidx + i) > max_pri Then
        pri_x = i
        pri_y = j
        max_pri = m_pri(Jidx + i)
    End If
Next i: Next j

查找最相似的补丁:

min = 99999999

For j = PatchT To PatchB: Jidx = j * m_width: For i = PatchL To PatchR
    If m_source(Jidx + i) Then
        sum = 0
        For iter_y = -Winsize To Winsize: target_y = pri_y + iter_y
            If target_y > 0 And target_y < m_height Then
                target_y = target_y * m_width: For iter_x = -Winsize To Winsize: target_x = pri_x + iter_x
                    If target_x > 0 And target_x < m_width Then
                        Tidx = target_y + target_x
                        If m_mark(Tidx) >= 0 Then
                            source_x = i + iter_x
                            source_y = j + iter_y
                            Sidx = source_y * m_width + source_x
                            temp_r = m_r(Tidx) - m_r(Sidx)
                            temp_g = m_g(Tidx) - m_g(Sidx)
                            temp_b = m_b(Tidx) - m_b(Sidx)
                            sum = sum + temp_r * temp_r + temp_g * temp_g + temp_b * temp_b
                        End If
                    End If
                Next iter_x
            End If
        Next iter_y

        If sum < min Then: min = sum: patch_x = i: patch_y = j
    End If
Next i: Next j

步骤3:更新置信度值

在用新的像素值填充块ΨpΨ之后,以thepˆ界定的区域中的置信度C(p)如下更新:

这个简单的更新规则使我们能够在没有图像特定参数的情况下测量填充前沿上色块的相对置信度。随着填充的进行,置信度值衰减,这表明我们不太确定目标区域中心附近的像素的颜色值。在此处实现(以及所有其他必要的更新):

x0 = -Winsize
For iter_y = -Winsize To Winsize: For iter_x = -Winsize To Winsize
    x0 = patch_x + iter_x
    y0 = patch_y + iter_y
    x1 = pri_x + iter_x
    y1 = pri_y + iter_y
    X1idx = y1 * m_width + x1
    If m_mark(X1idx) < 0 Then
        X0idx = y0 * m_width + x0
        PicAr1(x1, y1) = m_color(X0idx)
        m_color(X1idx) = m_color(X0idx)
        m_r(X1idx) = m_r(X0idx)
        m_g(X1idx) = m_g(X0idx)
        m_b(X1idx) = m_b(X0idx)
        m_gray(X1idx) = CDbl((m_r(X0idx) * 3735 + m_g(X0idx) * 19267 + m_b(X0idx) * 9765) / 32767)
        m_confid(X1idx) = ComputeConfidence(pri_x, pri_y)
    End If
Next iter_x: Next iter_y

For Y = IIf(pri_y - Winsize - 2 > 0, pri_y - Winsize - 2, 0) To IIf(pri_y + Winsize + 2 < m_height - 1, pri_y + Winsize + 2, m_height - 1): Yidx = Y * m_width: For X = IIf(pri_x - Winsize - 2 > 0, pri_x - Winsize - 2, 0) To IIf(pri_x + Winsize + 2 < m_width - 1, pri_x + Winsize + 2, m_width - 1)
    m_mark(Yidx + X) = IIf(PicAr1(X, Y).rgbRed = MaskRed And PicAr1(X, Y).rgbgreen = MaskGreen And PicAr1(X, Y).rgbBlue = MaskBlue, -1, Source)
Next X: Next Y

For Y = IIf(pri_y - Winsize - 2 > 0, pri_y - Winsize - 2, 0) To IIf(pri_y + Winsize + 2 < m_height - 1, pri_y + Winsize + 2, m_height - 1): Yidx = Y * m_width: For X = IIf(pri_x - Winsize - 2 > 0, pri_x - Winsize - 2, 0) To IIf(pri_x + Winsize + 2 < m_width - 1, pri_x + Winsize + 2, m_width - 1)
    If m_mark(Yidx + X) = -1 Then
        Found = (Y = m_height - 1 Or Y = 0 Or X = 0 Or X = m_width - 1) Or m_mark(Yidx + X - 1) = Source Or m_mark(Yidx + X + 1) = Source Or m_mark((Y - 1) * m_width + X) = Source Or m_mark((Y + 1) * m_width + X) = Source
        If Found Then: Found = False: m_mark(Yidx + X) = -2
    End If
Next X: Next Y

For i = IIf(pri_y - Winsize - 3 > 0, pri_y - Winsize - 3, 0) To IIf(pri_y + Winsize + 3 < m_height - 1, pri_y + Winsize + 3, m_height - 1): Yidx = i * m_width: For j = IIf(pri_x - Winsize - 3 > 0, pri_x - Winsize - 3, 0) To IIf(pri_x + Winsize + 3 < m_width - 1, pri_x + Winsize + 3, m_width - 1)
    If m_mark(Yidx + j) = -2 Then m_pri(Yidx + j) = ComputeConfidence(j, i) * ComputeData(j, i)
Next j: Next i

完整的代码

这是可运行的代码,并带有库的源代码作为注释。

该代码被调用

inpaint(infile, outfile, blocksize, windowsize, r, g, b)

例子包括以下形式

;~ inpaint("gothic_in.png", "gothic_out.png")
;~ inpaint("starry_in.png", "starry_out.png")
;~ inpaint("scream_in.png", "scream_out.png")
;~ inpaint("mona_in.png", "mona_out.png")
;~ inpaint("maze_in.png", "maze_out.png")
;~ inpaint("checker_in.png", "checker_out.png")

只是取消注释要使用CTRL+ 运行的示例Q

官方测试文件

该算法被调整为每个图像。因此,默认值(以及默认掩码)完全次优。选择默认值,以便可以在合理的时间内处理每个样本。我强烈建议使用形状不规则的口罩和更好的窗口尺寸。点击图片放大!

棋盘

美国哥特式

迷宫

蒙娜丽莎

(可怕的面具)

惊叫

星空

实际例子

这些都使用自定义手绘蒙版。

如果您想看到其他有趣的图像,请发表评论。

EBII改进

EBII有多种变体,由各种研究人员创建。AnkurKumar Patel收集了有关各种EBII改进的论文[24],引起了我的注意。

具体来说,论文“ 针对基于示例的图像修复的改进的鲁棒算法[25]提到了优先权值加权的两个改进。

改善

有效修改在算法的第1步(请参见上文)中,并使用以下方法对此像素的优先级进行C(p)D(p)扩展:

在式为C ^d以上给出,并且分别是归一化因子(例如,α= 255),等照度线矢量,单位矢量正交于前在点p。

进一步,

优先级函数定义为正则化置信度C(p)和新数据项D(p)的权重之和。其中α是调整系数,定义为满足0Rp(p):

其中α和β分别是置信度和数据项的分量权重。注意,α+β= 1

客观评分

但是,真正有趣的是,本文包含一种用于评估EBII算法性能的提议方法(并且很简单!)。不过,将其与一粒盐一起使用,因为这是论文作者自己选择的一种方法,目的是验证所提出的方差方法的有效性以及对若干图像的改进。

通过比较恢复图像和原始图像之间的PSNR(峰值信噪比[26])来执行结果评估。通常,PSNR值越高,修复后的图像与原始图像的相似度就越大。计算PSNR的公式如下:

这些是他们使用的惊人的2(两张!)真实世界测试图像:

结论与纸张本身的质量一样令人失望。它显示几乎没有改善。这里最主要的是针对此类挑战(以及其他图像修复挑战)的可能的对象评分方法:

+-------+---------------+----------+
| Image | EBII Original | Improved |
+-------+---------------+----------+
|     1 |       52.9556 |  53.7890 |
|     2 |       53.9098 |  53.8989 |
+-------+---------------+----------+

有待研究

(特定于EBII)

a)预处理

一切都归结为“魔术擦除”原理,即该算法应“对一切正常”。我对此的幼稚解决方案是基于颜色的放大(请参见上文),但是有更好的方法。我正在考虑识别所有可追踪纹理像素的几何平均值,以自动调整窗口大小,并使图章大小(也是我的改进)取决于纹理像素整个图像的分辨率。研究必须在这里进行。

b)后处理

原始作者已经非常出色地将所有想到的后期处理过滤器拆封了。今天,我尝试了一些其他方法,受到了永远不可思议的蒙娜丽莎的启发(感谢undergroundmonorail)。如果拍摄已修复的区域,并对所有奇怪的颜色块应用新的蒙版,并将其输入去斑点算法,则结果几乎完美。我可能会在将来的某个时间进行探索。


[X] -A. Criminisi,P。Perez,K。Toyama通过基于示例的绘画移除对象
[1]-M. Ashikhmin。合成自然纹理。在过程中。ACM症状。互动3D图形,第217-226页,北卡罗莱纳州研究三角园,2001年3月。
[5] — M. Bertalmio,L。Vese,G。Sapiro和S. Osher。同时修复结构和纹理图像。出现,2002
[6] — R. Bornard,E。Lecan,L。Laborelli和JH。切诺特。静止图像和图像序列中缺少数据校正。在法国ACM多媒体公司,2002年12月。
[7] — TF Chan和J. Shen。通过曲率驱动的扩散(CDD)进行非纹理修补。J.视觉通讯。图片代表。,4(12),2001。
[8]-JS de Bonet。用于纹理图像分析和合成的多分辨率采样程序。在过程中。ACM Conf。比较 图形(SIGGRAPH),第31卷,第361–368页,1997年。
[9] — A. Efros和WT Freeman。图像缝,用于纹理合成和转移。在过程中。ACM Conf。比较 Graphics(SIGGRAPH),第341–346页,Eugene Fiume,2001年8月。
[10] — A. Efros和T. Leung。通过非参数采样进行纹理合成。在过程中。ICCV,第1033至1038页,希腊克尔基拉,1999年9月。
[11] — WT Freeman,EC Pasztor和OT Carmichael。学习低视力。诠释 J. Computer Vision,40(1):25-47,2000。
[12] — D. Garber。纹理分析和纹理合成的计算模型。博士论文,大学。美国南加州,1981年。
[13] —哈里森(P. Harrison)。用于重新合成复杂纹理的非分层过程。在过程中。诠释 Conf。中欧公司 图形,Visua。和比较。Vision,比尔森,捷克共和国,2001年2月。
[14] — DJ Heeger和JR Bergen。基于金字塔的纹理分析/合成。在过程中。ACM Conf。比较 图形(SIGGRAPH),第29卷,第229-233页,加利福尼亚州洛杉矶,1995年。
[15] — A. Hertzmann,C。Jacobs,N。Oliver,B。Curless和D. Salesin。图像类比。在过程中。ACM Conf。比较 Graphics(SIGGRAPH),Eugene Fiume,2001年8月。
[16] — H. Igehy和L. Pereira。通过纹理合成进行图像替换。在过程中。诠释 Conf。图像处理,第Ⅲ:186-190页,1997年。
[17] — G. Kanizsa。愿景中的组织。1979年,纽约普拉格。
[19] — L. Liang,C。Liu,Y.-Q。徐,郭宝和H.-Y. 嘘 通过基于补丁的采样进行实时纹理合成。在ACM Transactions on Graphics,2001年。
[22] — L.-W.。Wey和M. Levoy。使用树结构矢量量化进行快速纹理合成。在过程中。ACM Conf。比较 Graphics(SIGGRAPH),2000年。
[23] — A. Zalesny,V。Ferrari,G。Caenen和L. van Gool。并行复合纹理合成。在Texture 2002研讨会上-(与ECCV02结合使用),哥本哈根,丹麦,2002年6月。
[24] -AkurKumar Patel,古吉拉特理工大学,计算机科学和工程学
[25]- 改进的基于示例图像修补的鲁棒算法
[26]- 维基百科,峰值信噪比


30
太棒了。繁星之夜真好。不过,那
名蒙娜丽莎

8
首先让我说“哦,天哪,这太不可思议了”。第二:我在这里的另一个问题上已经评论过“蒙娜丽莎(Mona Lisa)是一些SCP屎”,但实际上这只猫头鹰看起来像可以出现在SCP Wiki上的东西。
地下

3
您提到的带引号的段落可以用引号引起吗?
trichoplax '16

1
@trichoplax几乎每个句子中都有一些小的修改,它们不是精确的引号。考虑算法描述与组织相同。除非有修改或代码说明。我不想弄乱格式更多:)
mınxomaτ

2
当我试图在梦中非常仔细地观察某些事物时,有时事物会变得完全像这样。
jimmy23013 '16

45

Matlab的

这是一种简单的插值方法。这个想法是首先镜像补丁两侧的内容。然后,根据与相应边缘的距离对这些镜像像素进行插值:

棘手的部分是找到一个不错的插值权重。经过一番摸索之后,我想出了一个有理函数,该函数在所有边上都为零,除了我们所镜像的那个。然后,通过三次多项式对它进行平滑处理:

这种简单的方法在“自然”图像上的表现出奇地好,但是一旦您遇到锐利的边缘,游戏就结束了。在美国的哥特式示例中,干草叉的尖峰与像素网格很好地对齐,这使它看起来非常漂亮,但否则会更糟。

因此,这里的结果是:

最后,代码:

imgfile= 'filename.png';
maskfile = [imgfile(1:end-4),'_mask.png'];
img = double(imread(imgfile));
mask = rgb2gray(imread(maskfile));
%% read mask
xmin = find(sum(mask,1),1,'first');
xmax = find(sum(mask,1),1,'last');
ymin = find(sum(mask,2),1,'first');
ymax = find(sum(mask,2),1,'last');
%% weight transformation functiosn
third = @(x)-2* x.^3 + 3* x.^2;
f=@(x)third(x);
w=@(x,y)y.*(x-1).*(y-1)./( (x+y).*(x+1-y));

for x=xmin:xmax
    for y=ymin:ymax
        %Left Right Up Down;
        P = [img(y,xmin-(x-xmin)-1,:);img(y,xmax+(xmax-x)+1,:);img(ymin-(y-ymin)-1,x,:);img(ymax+(ymax-y)+1,x,:)];
        % normalize coordinates
        rx = (x-xmin)/(xmax-xmin); 
        ry = (y-ymin)/(ymax-ymin);
        % calculate the weights
        W = [w(rx,ry),w(1-rx,ry),w(ry,rx),w(1-ry,rx)]';
        W = f(W);
        W(isnan(W))=1;
        img(y,x,:) = sum(bsxfun(@times,P,W),1)/sum(W); 
    end
end
imshow(img/255);
imwrite(img/255,[imgfile(1:end-4),'_out.png']);

10
蒙娜丽莎(Mona Lisa)吓到我了。
Andras Deak

46
莫诺斯(Ḿo̢̎̓̀ǹ̰͎̣͙a̤̩̖̞̝ͧ̈ͤͤḶ̖̠̮̘̹̠̾̇ͣi̛̥͕̱͋͌ş̠͔̏̋̀ạ̫͕͎ͨͮͪ̐͡ͅ
–mınxomaτ16

8
蒙娜丽莎(Mona Lisa)是一些SCP狗屎
地下

1
格仔的图像看起来非常酷,恕我直言。
ETHproductions 2016年

1
如果您以此赢得自己的挑战,我不会感到惊讶。这是一个非常好的解决方案。
Alex A.

25

Mathematica

这使用了Mathematica的Inpaint功能。因为Mathematica本身承担了所有繁重的工作,所以这是社区Wiki。

inPaint(下图)是的简单改编Inpaint。对于彩色绘画/照片,它使用默认设置“ TextureSynthesis”。如果它检测到图片为黑白(因为图片的图像数据与图片的二进制形式的图像数据相同),则它将图像二值化并应用“ TotalVariation”补丁。该If条款适用于两种BinarizeIdentity到图片。(该Identity函数不变地返回其参数。)

inPaint[picture_, mask_] :=  
 If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@
  Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]

输入图像和遮罩作为的参数inPaintPartition并且Grid仅用于格式化目的。

输入

输出已打补丁。之后,没有修饰图像inPaint

输出


4
可能是巧合,但是我对迷宫的表现感到惊讶!
瑕疵的

1
@flawr我会扔这样的东西来弄乱这个解决方案;)(谁知道?那些黑白混蛋真是莫名其妙。)
Andras Deak

17
我认为这不应该是社区Wiki。
丹尼斯

律师,是的,Inpaint似乎在整个黑白图像中寻找对称性。– DavidC 9小时前
DavidC

确定黑白算法不会在任何地方牺牲山羊吗?如果掩盖所有图像,地球上怎么会猜测图像的中心结构?
Andras Deak

18

Python 2和PIL

该程序将北,南,东和西区域的副本混合在一起,以创建替换像素,这些像素使用本地图像区域中的颜色,纹理和阴影。

该示例输出:

代码首先找到补丁的边界框。然后,对于要生成的每个像素,它基于4个周围区域的加权和计算每个通道的颜色(RGB)。

import sys
from PIL import Image

infile, maskfile, outfile = sys.argv[1:4]
imageobj = Image.open(infile)
maskobj = Image.open(maskfile)
image = imageobj.load()
mask = maskobj.load()

assert imageobj.size == maskobj.size
W, H = imageobj.size
pixels = [(x,y) for x in range(W) for y in range(H)]
whitepart = [xy for xy in pixels if sum(mask[xy]) > 230*3]
xmin = min(x for x,y in whitepart)
xmax = max(x for x,y in whitepart)
ymin = min(y for x,y in whitepart)
ymax = max(y for x,y in whitepart)
xspan = xmax - xmin + 1
yspan = ymax - ymin + 1

def mkcolor(channel):
    value = image[(xmin-dx, y)][channel] * 0.5*(xspan - dx)/xspan
    value += image[(xmax+1 + xspan - dx, y)][channel] * 0.5*dx/xspan
    value += image[(x, ymin-dy)][channel] * 0.5*(yspan - dy)/yspan
    value += image[(x, ymax+1 + yspan - dy)][channel] * 0.5*dy/yspan
    return int(value)

for dx in range(xspan):
    for dy in range(yspan):
        x = xmin + dx
        y = ymin + dy
        image[(x, y)] = (mkcolor(0), mkcolor(1), mkcolor(2))

imageobj.save(outfile)

3
这个蒙娜丽莎也很恐怖!蒙娜丽莎(Mona Lisas)在这场挑战中难道注定会感到恐惧吗?
undergroundmonorail

@undergroundmonorail我猜想计算机生成的偶然面孔恰好是在怪异山谷的深处。
Andras Deak

您从哪里获得PIL?
Elliot A.

@ElliotA。我的理解是,适当的PIL已死,但它是开源的,因此以“ Pillow”为名继续存在。如果您使用Google“ python枕头”,则应该找到它。
地下

13

Python 3,PIL

该程序使用sobel运算符,并以此为基础在图像上绘制线条。

sobel运算符会找到每个边缘的角度,因此任何伸入未知区域的边缘都应继续。

from PIL import Image, ImageFilter, ImageDraw
import time
im=Image.open('2.png')
im1=Image.open('2 map.png')
a=list(im.getdata())
b=list(im1.getdata())
size=list(im.size)
'''
def dist(a,b):
    d=0
    for x in range(0,3):
        d+=(a[x]-b[x])**2
    return(d**0.5)
#'''
C=[]
d=[]
y=[]
for x in range(0,len(a)):
    if(b[x][0]==255):
        C.append((0,0,0))
    else:
        y=(a[x][0],a[x][1],a[x][2])
        C.append(y)
im1.putdata(C)
k=(-1,0,1,-2,0,2,-1,0,1)
k1=(-1,-2,-1,0,0,0,1,2,1)
ix=im.filter(ImageFilter.Kernel((3,3),k,1,128))
iy=im.filter(ImageFilter.Kernel((3,3),k1,1,128))
ix1=list(ix.getdata())
iy1=list(iy.getdata())
d=[]
im2=Image.new('RGB',size)
draw=ImageDraw.Draw(im2)
c=list(C)
Length=0
for L in range(100,0,-10):
    for x in range(0,size[0]):
        for y in range(0,size[1]):
            n=x+(size[0]*y)
            if(c[n]!=(0,0,0)):
                w=(((iy1[n][0]+iy1[n][1]+iy1[n][2])//3)-128)
                z=(((ix1[n][0]+ix1[n][1]+ix1[n][2])//3)-128)
                Length=(w**2+z**2)**0.5
                if Length==0:
                    w+=1
                    z+=1
                Length=(w**2+z**2)**0.5
                w/=(Length/L)
                z/=(Length/L)
                w=int(w)
                z=int(z)
                draw.line(((x,y,w+x,z+y)),c[n])

d=list(im2.getdata())
S=[]
d1=[]
A=d[0]
for x in range(0,size[0]):
    for y in range(0,size[1]):
        n=y+(size[1]*x)
        nx=y+(size[1]*x)-1
        ny=y+(size[1]*x)-size[0]
        if d[n]==(0,0,0):
            S=[0,0,0]
            for z in range(0,3):
                S[z]=(d[nx][z]+d[ny][z])//2
            #print(S)
            d1.append(tuple(S))
        else:
            d1.append(tuple(d[n]))
d=list(d1)
im2.putdata(d)
#im2=im2.filter(ImageFilter.GaussianBlur(radius=0.5))
d=im2.getdata()
f=[]
#'''
for v in range(0,len(a)):
    if(b[v][0]*b[v][1]*b[v][2]!=0):
        f.append(d[v])
    else:
        f.append(C[v])
#'''
im1.putdata(f)
im1.save('pic.png')

同时,这是示例图像。

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

蒙娜丽莎(Mona Lisa)蒙娜丽莎(Lona Msa)

在此处输入图片说明 上图中的区域像仙人掌一样光滑

恒定的颜色不是很好。

在此处输入图片说明

在此处输入图片说明


1
哦,能否请您添加B / W测试用例?
瑕疵的

2
在“星夜”上看起来真的很棒。
SuperJedi224 '02

1
哇,这看起来真不可思议!补丁仍然引人注目,但是是个很棒的新主意!到目前为止我最喜欢的=)
瑕疵的,

8
+1为“蒙娜丽莎蒙娜丽莎蒙娜娜莉莎玛莉莎娜莉莎莉莎玛莉娜莎莉娜莎莉莎”
mbomb007 '16

2
您将赢得IMO“最可怕的蒙娜丽莎”竞赛。0_o
DLosc

8

Python 2

简单的python脚本,使用间隙外的像素值创建补丁。它从像素行和列的末尾获取颜色值,并使用与这些像素的距离计算加权平均值。

输出不是很漂亮,但是很艺术

img1 img2 img3 img4 img5 img6

然后,代码:

IMGN = "6"

IMGFILE = "images/img%s.png" % (IMGN,)
MASKFILE = "images/img%s_mask.png" % (IMGN,)

BLUR = 5


def getp(img,pos):
    return img.get_at(pos)[:3]
def setp(img,pos,color):
    img.set_at(pos, map(int, color))

def pixelavg(L):
    return map(int, [sum([i[q] for i in L])/float(len(L)) for q in [0,1,2]])
def pixelavg_weighted(L, WL):   # note: "inverse" weights. More weight => less weight
    # colors [sum, max]
    color_data = [[0, 0], [0, 0], [0, 0]]
    for color,weight in zip(L, WL):
        for i in [0, 1, 2]: # r,g,b
            color_data[i][0] += inv_w_approx(weight) * color[i]
            color_data[i][1] += inv_w_approx(weight) * 255
    return [255*(float(s)/m) for s,m in color_data]
def inv_w_approx(x):
    return (1.0/(x+1e-10))

import pygame
image = pygame.image.load(IMGFILE)
mask = pygame.image.load(MASKFILE)

size = image.get_size()
assert(size == mask.get_size())

# get square from mask
min_pos = None
max_pos = [0, 0]
for x in range(size[0]):
    for y in range(size[1]):
        if getp(mask, [x, y]) == (255, 255, 255):
            if min_pos == None:
                min_pos = [x, y]
            max_pos = [x, y]
if not min_pos:
    exit("Error: no mask found.")
# patch area info
patch_position = min_pos[:]
patch_size = [max_pos[0]-min_pos[0], max_pos[1]-min_pos[1]]

# remove pixels from orginal image (fill black)
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(image, [patch_position[0]+dx, patch_position[1]+dy], [0, 0, 0])

# create patch
patch = pygame.Surface(patch_size)

# take pixels around the patch
top = [getp(image, [patch_position[0]+dx, patch_position[1]-1]) for dx in range(patch_size[0])]
bottom = [getp(image, [patch_position[0]+dx, patch_position[1]+patch_size[1]+1]) for dx in range(patch_size[0])]
left = [getp(image, [patch_position[0]-1, patch_position[1]+dy]) for dy in range(patch_size[1])]
right = [getp(image, [patch_position[0]+patch_size[0]+1, patch_position[1]+dy]) for dy in range(patch_size[1])]

cpixels = top+left+right+bottom

# set area to average color around it
average = [sum([q[i] for q in cpixels])/float(len(cpixels)) for i in [0, 1, 2]]

for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(patch, [dx, dy], average)

# create new pixels
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(patch, [dx, dy], pixelavg_weighted([top[dx], bottom[dx], left[dy], right[dy]], [dy, patch_size[1]-dy, dx, patch_size[0]-dx]))

# apply patch
for dx in range(patch_size[0]):
    for dy in range(patch_size[1]):
        setp(image, [patch_position[0]+dx, patch_position[1]+dy], getp(patch, [dx, dy]))

# blur patch?
for r in range(BLUR):
    for dx in range(patch_size[0]):
        for dy in range(patch_size[1]):
            around = []
            for ddx in [-1,0,1]:
                for ddy in [-1,0,1]:
                    around.append(getp(image, [patch_position[0]+dx+ddx, patch_position[1]+dy+ddy]))
            setp(patch, [dx, dy], pixelavg(around))

    # apply blurred patch
    for dx in range(patch_size[0]):
        for dy in range(patch_size[1]):
            setp(image, [patch_position[0]+dx, patch_position[1]+dy], getp(patch, [dx, dy]))

# save result
pygame.image.save(image, "result.png")

目前,您看到的是水平/垂直条纹,也许可以通过添加其他方向来改善它!
瑕疵的

我实际上尝试过,但是无法获得良好的效果,所以我决定只对图像进行模糊处理:D
Hannes Karppila,2016年

19
最后,一个蒙娜丽莎(Mona Lisa)并没有吓死我,但看起来却像是一个被捕的凶手。
Andras Deak

6

Mathematica

Inpaint

碰巧的是Mathematica具有一个内置函数来执行此任务,我的意思

Inpaint[image, region]

  • 修饰image与中的非零元素相对应的部分region

默认情况下,它使用“使用随机采样的最佳拟合纹理合成方法”,该方法在绘画上产生好的结果,但对于迷宫和棋盘格则效果不佳:

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

进行这些设置并不能使我在所有图像上的质量都有所提高,所以我只使用了默认设置(以节省字节codegolf.se,毕竟是这样!)。


23
Mathematica确实具有内置功能 ” ...惊喜,惊喜;)
Andras Deak

对于迷宫和棋盘,最好同时使用“ TotalVariation”方法Binarize(以消除灰色污迹)。试试这个: methods = {"TextureSynthesis", "Diffusion", "FastMarching", "NavierStokes", "TotalVariation"};g[pic_, mask_] := Join[{Labeled[Framed@pic, "Original"]}, Labeled[ Binarize@Inpaint[pic, mask, Method -> #], #] & /@ methods]
DavidC

@DavidC我尝试了其他方法,但只TextureSynthesis在绘画上看起来不错;而且我认为我们不允许为每个测试用例调整设置。(如果有可能,那么我们就可以平凡提供缺少的部分作为“设置”。)
2012rcampion

迷宫和棋盘格的结果真的让我感到困惑。为什么Mathematica对缺失区域的重建如此不规则和不对称?
张大卫

这将自动检测图像是否为黑白,并进行适当的调整(二进制和“ TotalVariation”方法)。 inPaint[picture_, mask_] := If[bw = ImageData@Rasterize[Binarize[picture]] == ImageData[picture], Binarize, Identity]@ Inpaint[picture, mask, Method -> If[bw, "TotalVariation", "TextureSynthesis"]]
DavidC

5

Python3

这个答案 Ulyanov等人的论文“ Deep Image Prior”中实现了这一思想。(CVPR 2018)在本文中,他们探讨了一种想法,即设计性能良好的神经网络进行图像处理的方式紧密反映了我们对自然图像的外观(“先验”分布)的想法。

他们提出了一种可用于修复以及去除噪声和伪影的方法,该方法仅使用给定的图像而无需任何其他数据的训练。实际概念非常简单:通过仅惩罚某些给定蒙版之外的错误,训练网络输出所需的图像(对于某些固定的随机噪声作为输入)。如果您想消除噪音,您只需要掩盖任何东西,而只需在训练中尽早停止即可。

对于修补,您要遮盖要修补和训练的零件,直到收敛为止。它当然不是最新技术,但是由于想法的简单性和出色的性能,我仍然想将其发布并在此处发布。在我的实验中,较大补丁的修复效果不佳,但是对于较小的片段,结果可能更具说服力。

我实现了这个采用流行的U型网结构GitHub上jaxony。可以在下面找到用于训练和处理图像的代码。

训练

这是培训过程的可视化。每帧都处于一定数量的迭代状态:

例子

请注意,在一个cpu上,仅一张图像就可能需要花费几个小时才能运行,而启用了cuda的gpu可能要花更少的时间。

import torch
import numpy as np
unet = __import__('unet-pytorch')
import PIL.ImageOps
#specify device (cpu/cuda)
device = "cpu"
#specify file and size
file = 'mona'
size = 512 #pad to this size (no smaller than original image), must be divisible by 2^5
img_pil = PIL.Image.open(file +'.png').convert('RGB')
mask_pil = PIL.Image.open(file +'-mask.png').convert('RGB')

net = unet.UNet(num_classes=3, in_channels=32, depth=6, start_filts=64).to(device)
h,w = img_pil.size
pad = (0, 0, size - h, size - w)
img = PIL.ImageOps.expand(img_pil, border=pad)
img = torch.Tensor(np.array(img).transpose([2, 0, 1])[None, :, :, :].astype(np.double)).to(device)
mask = PIL.ImageOps.expand(mask_pil, border=pad)
mask = torch.Tensor((np.array(mask)==0).transpose([2, 0, 1])[None, 0:3, :, :].astype(np.double)).to(device)
mean = img.mean()
std = img.std()
img = (img - mean)/std
optimizer = torch.optim.Adam(net.parameters(), lr=0.0001)
criterion = torch.nn.MSELoss()
input = torch.rand((1, 32, size, size)).to(device)
for it in range(5000):
    if it == 1000:
        optimizer.param_groups[0]['lr'] = 0.00003
    out = net(input)
    loss = criterion(out * mask, img * mask)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
out = out.detach().cpu().numpy()[0].transpose([1,2,0])*std.item() + mean.item()
out = np.clip(out, 0, 255).astype(np.uint8)[0:w, 0:h, :]
mask_np = (np.array(mask_pil) > 0).astype(np.uint8)
img_np = np.array(img_pil)
inpaint = (img_np * (1-mask_np) + mask_np * out).astype(np.uint8)
PIL.Image.fromarray(inpaint).save('./{}_inpainted.png'.format(file))

Deep Image Prior使用的是不同于U-Net的东西吗?似乎他们会得到更好的结果
仅ASCII格式的

另外,您是否尝试过使用Deep Image Prior的代码
仅ASCII的

@@ only-ASCII它们在论文中指出,它们确实确实确实主要使用U-Net,但是我找不到它们所使用的确切参数。他们可能使用了容量更大的网。我只有一台功率非常有限的计算机。因此,我必须选择仍然适合内存的参数,并且训练时间不会太长。我不确定确切需要多长时间,但是在我使用的计算机(仅使用CPU)上,这些图像需要花费几天的时间。(如果你有一个备用的CUDA GPU启用让我知道:)
flawr

我还怀疑由于具有矩形蒙版的网络设计也不理想(并且较小的蒙版也可能看起来更好),例如,如果将前几个图像与后两个图像(不使用矩形蒙版)进行比较的话。
flawr

4

带有OpenCV的Python

OpenCV具有一个称为inpaint的功能。有两种类型的修复,我将使用快速进行方法。根据文档,该算法的工作方式如下:

考虑图像中要修复的区域。算法从该区域的边界开始,并进入该区域内部,然后逐渐填充边界中的所有内容。需要在附近像素上的一个小邻域进行修补。该像素被附近所有已知像素的归一化加权总和所代替。权重的选择很重要。那些位于该点附近,边界法线附近的像素和那些位于边界轮廓线上的像素将获得更多权重。修复像素后,将使用快速行进方法将其移动到下一个最近的像素。FMM确保首先修复已知像素附近的那些像素,以便像手动启发式操作一样工作。

这是代码*:

import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('gothic.jpg')
b,g,r = cv2.split(img)
img2 = cv2.merge([r,g,b])
mask = cv2.imread('mask.jpg',0)
dst = cv2.inpaint(img2,mask,3,cv2.INPAINT_TELEA)
(h, w) = dst.shape[:2]
center = (w / 2, h / 2)
# rotate the image by 180 degrees
M = cv2.getRotationMatrix2D(center, 180, 1.0)
rotated = cv2.warpAffine(dst, M, (w, h))
plt.imshow(rotated)

请注意,出于绘图原因,我如何将BGR转换为RGB。另外,我旋转它。结果如下:

哥特

星夜 惊叫 另一个令人毛骨悚然的蒙娜丽莎!

蒙娜丽莎回来了!

1行

检查器

如您所见,这两种颜色都不是最好的选择。


蒙娜丽莎有一个换装
康纳尔奥布莱恩

3

爪哇

颜色平均方法。可能可以改善。

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Scanner;

import javax.imageio.ImageIO;


public class ImagePatcher{
    public static void main(String[]args) throws Exception{
        Scanner in=new Scanner(System.in);
        int white=Color.WHITE.getRGB();
        int black=Color.BLACK.getRGB();
        BufferedImage image=ImageIO.read(new File(in.nextLine())),mask=ImageIO.read(new File(in.nextLine()));
        assert(image.getWidth()==mask.getWidth()&&image.getHeight()==mask.getHeight());
        boolean bool=true;
        while(bool){
            bool=false;
        for(int x=0;x<image.getWidth();x+=2){
            for(int y=0;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }           
        }
        for(int x=0;x<image.getWidth();x+=2){
            for(int y=1;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }
        }
        for(int x=1;x<image.getWidth();x+=2){
            for(int y=0;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }           
        }
        for(int x=1;x<image.getWidth();x+=2){
            for(int y=1;y<image.getHeight();y+=2){
                if(mask.getRGB(x,y)!=white)continue;
                int r=0,g=0,b=0,n=0;
                for(int dx=-1;dx<=1;dx++){
                    if(x+dx<0)continue;
                    if(x+dx>=image.getWidth())continue;
                    for(int dy=-1;dy<=1;dy++){
                        if(y+dy<0)continue;
                        if(y+dy>=image.getHeight())continue;
                        if(mask.getRGB(x+dx,y+dy)==white)continue;
                        Color c=new Color(image.getRGB(x+dx,y+dy));
                        r+=c.getRed();
                        g+=c.getGreen();
                        b+=c.getBlue();
                        n++;
                    }
                }
                if(n==0){bool=true;continue;}
                Color c=n>0?new Color(r/n,g/n,b/n):new Color(100,100,100);
                image.setRGB(x,y,c.getRGB());
                mask.setRGB(x, y, black);
            }
        }
        };
        ImageIO.write(image, "png", new File("output.png"));
    }
}

结果:

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明


2
为什么总是总是以这种特定角度获得线条?左上角似乎匹配得比较好,而右下角根本不匹配。
瑕疵的

我认为这与我在该地区进行迭代的方式有关。我最终可能会改变它。
SuperJedi224 '16

它总是看起来像梯形。
ericw31415 '16

@ ericw31415这是迭代顺序的产物。
SuperJedi224'2016-4-24
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.