在给定完美的深度图的情况下,如何生成散景来模拟浅景深?

我尝试过的方法是:


使用Blender和相应的工具生成示例场景深度图






使用Python生成用于每个像素的高斯模糊的sigma(标准差):

完全聚焦。

sigma_map = np.array(np.round(abs(np.array(depth, dtype='float32') - preferred_focus_depth)).clip(0,255), dtype='uint8')





黑色= 0,不模糊;白色= 255,大模糊


在每个像素处应用高斯模糊(使用sigma_map),这就是我得到的



/>观察到的缺陷:边缘附近的平均果粒附近呈粉红色,因此粉红色流血。如何避免这种情况?

注意:仔细观察右边的放大图像,它并没有完全模糊。我想保持原样的像素仍然没有被修改(即使由于边缘附近的模糊而看起来也很模糊)。

关于Pixel 2纵向模式的博客说:“实际上从概念上讲,模糊是最简单的部分”,那么还有什么更简单的方法吗?

评论

由于没有代码或任何东西,因此很难提供建议。我最好的猜测是,在粉红色立方体上方/之后的像素的高斯模糊中,您仍从所有相邻像素(包括粉红色立方体)中采样。因此,它会以粉红色模糊。因此,在内核中,如果相邻像素来自非模糊对象,则需要忽略它们。

很抱歉没有发布任何代码:未在此处添加代码,因为我只是做普通的旧卷积,但每个像素具有不同的内核。 “因此,在您的内核中,如果相邻像素来自非模糊对象,则需要忽略它们”:我该怎么做?

这取决于您正在使用什么。如果这是glsl着色器,那么您可能拥有称为sigma的纹理,并且可以从渲染的图像中读取并写入输出。因此,您可以将像素颜色与sigma值相乘,然后再将其添加到输出中,而不是从sigma纹理的相邻像素中读取并将其添加到输出中。由于sigma = 0可以实现完美对焦,因此可以防止粉红色泄漏到背景上。请注意,您仍然需要调整内核规范化,否则立方体周围的图片会变暗

看看这篇博客文章:Dennis Gustafsson单次通过散景景深。它逐步开发了从深度散景的过滤器,包括在更简单的实现中遇到的各种工件以及如何修复它们。

#1 楼

您所讨论的博客文章不是关于为计算机生成的图像生成bokeh。相反,它是根据智能手机相机拍摄的图像生成令人信服的景深效果,因为人像需要这种效果才能使主体脱颖而出。通常,它通过将图像分为两部分来工作。一部分是主题,不应模糊。另一部分是背景,应该使其模糊,但是对象的像素应该没有影响,因此权重为零。稍微复杂一些的算法可能会使整个图像的模糊内核大小可变以创建更平滑的过渡,但是仍然会忽略对象的像素。

在游戏和CGI中,后期处理景深的处理方式有所不同。有多种方法。一个简单的谷歌搜索显示多个。您可以像使用深度一样进行景深处理,而只是忽略不应该模糊以消除重影的像素。但是,这对于照相机内部如何发生景深并不正确。因此,为了获得一种好的算法来模拟景深,我将首先尝试说明您如何在实际的相机中获得景深效果。物镜,一个镜头和一个图像传感器。我们从侧面看它们。

光线在撞击物体时会沿许多不同的方向散射。为了简化,我们只讲一点。从这一点出发,我将以灰色固体显示入射到镜头的光线。

镜头将光线向单个点弯曲。这就是重点。

您可以看到焦点在传感器的前面。这导致来自物体上该点的光落在传感器的大部分上。如果焦点在传感器后面,则会发生同样的情况。但是,如果焦点位于传感器上,则传感器上只有一个点会落在光上。光落在传感器上的这个区域称为混乱圆(CoC)。这也是造成景深影响的原因。
要知道的一件事是,当物体上的点移动时,焦点也会移动。如果该点移近镜头,则焦点移近镜头,这将改变混乱圆的大小。这使您随着景深的模糊度逐渐变化,因为当物体从焦点区域移开时,混乱的圆会逐渐变大。

如果我们增加光圈,我们只需遮挡一部分光,这会使混乱的圈子变小。这使我们能够确定要入射多少光(使图像更亮或更暗)以及景深效果有多强。


由此我们可以得出结论关于如何实现景深的一些事情。


光进入相机镜头的每个点都有一个混乱的圆。
混乱的圆是面积。
当焦点移近或远离镜头或光圈改变大小时,混乱圆会改变大小。
混乱没有衰减。混乱圈内的每个点都具有相同的权重。这将导致框模糊。
如果焦点对准焦点,则其混淆圈将为零或很小,以至于无法察觉。

该算法非常简单。对于图像中的每个像素,我们使用深度来计算混淆圆。然后,我们将该像素的颜色添加到混乱圈中的所有其他像素中。但是,如果其他像素更靠近相机(较低的深度值),则它将阻挡来自我们像素的光,并且我们不应该将像素的颜色添加到阻挡像素中。

用伪代码看起来像这样;

// for each pixel in the all-focus image
for(x = 0; x < width; x++){
    for(y = 0; y < height; y++){
        // How much blur? Zero or close to it if it should be in focus.
        float CoCRadius = calcCoCRadius(depth(x, y));

        // For every pixel in the square with the size of CoCRadius.
        for(i = x - CoCRadius; i <= x + CoCRadius; i++){
            for(j = y - CoCRadius; j <= y + CoCRadius; j++){
                // Discard pixels outside of the shape. Square -> Circle
                if(isInBokehShape(i - x, j - y)){
                    // Discard pixels that would block it.
                    if(depth(i, j) >= depth(x, y){
                        // Add the colour.
                        newColour[i, j] += colour(x, y);
                        // Add the weight, for normalizing.
                        weight[i, j] += 1.0f;
                    }
                }
            }
        }

    }
}
//Normalize the values with the weight.
for(x = 0; x < width; x++){
    for(y = 0; y < height; y++){
        newColour[x, y] /= weight[x, y];
    }
}


这将为您提供非常真实的景深效果,而不会给您幻影效果。 $ calcCoCRadius $是一种计算混乱圆的半径(以像素为单位)的方法。可以是任何东西,但是如果您希望它对真实的相机是真实的,则可以使用此Wikipedia页面中的公式。 $ isInBokehShape $是一种返回点是否在bokeh形状内的布尔值的方法。对于这两个for循环,我们仅得到一个正方形,但是如果要获得圆形的散景,则需要丢弃位于该圆形之外的像素。这种丢弃也是保持跟踪权重并随后使用权重进行归一化的原因,因为我们不知道需要丢弃多少像素。如果您确实希望在混乱的圈子中衰减,例如,如果您想要高斯模糊,那么权重也很有用。

要知道的另一件重要事情是,此处列出的实现不是很最佳。问题基本上是您写入多个不同的像素。这会导致比赛条件。取而代之的是,最好对算法进行重新设计,以便只需要写一次即可。基本上,您遍历每个像素,然后检查所有其他像素(或最大CoC半径之内的像素)它们的CoC半径是否足够大以至于对像素的颜色有所贡献。这样,您还可以将值的归一化放在所有像素的第一个循环内,而不必稍后进行。

这应该是实现景深作为后期处理效果的一种很好的方法。还有其他方法,但这是一种。

评论


$ \ begingroup $
根据上面的公式,深度值较低的像素将不会从深度值较高的附近像素获得颜色(即,不会有bg渗入物体)。但是,经过仔细的思考和实验,我发现颜色不会仅在聚焦平面附近渗出,因此,即使在离聚焦平面稍远的平面上,对象的颜色也会渗入背景,反之亦然。图片说明在这里。我该如何处理?
$ \ endgroup $
– Saravanabalagi Ramachandran
18年7月3日在11:53

$ \ begingroup $
@SaravanabalagiRamachandran我给出的算法已经具有这种效果,即前景向背景出血的部分。但是,背景中流向前景的部分(因为前景仅变成会击中该特定像素的光的一部分)不在算法中。仅会使一个小细节变得更加复杂。还应该考虑的一点是,您不知道足够的信息(您只知道最近的对象的颜色)才能正确执行DoF,因此您必须做出一些妥协。
$ \ endgroup $
–bram0101
18年7月3日在17:07

$ \ begingroup $
CoC半径不代表像素分配能量的区域,而不是像素收集能量的区域吗?那么isInBokehShape不应该使用像素(i,j)的CoC半径而不是(x,y)的CoC半径吗?
$ \ endgroup $
–马特西亚
18年8月26日在16:57



$ \ begingroup $
@Matthias是的,实际上是正确的。我基于After Effects中的“相机镜头模糊”算法(但添加了深度检查),该算法使用$(x,y)$代替$(i,j)$。虽然,我确实认为,在大多数情况下,这并不重要,除非您要在物理上最精确,在这种情况下,最好不要将景深与景深图像进行对比。
$ \ endgroup $
–bram0101
18年8月26日在17:20

#2 楼

效果是否需要物理上准确?我以前使用作弊来实现这一目标。 @ bram0101的答案很好。如果您需要准确性,则一定要实现。通过简单地设置图像亮度的阈值并在高于阈值的每个点上居中放置一个半透明的多边形或圆,我已经能够使人眼前一亮。

您还需要考虑到距焦平面的距离。距离可用于控制散景的大小,在焦平面处为0,在更远的距离处为最大。

评论


$ \ begingroup $
我还没有完全明白,是单独的亮度(强度?)还是深度感知的?您是否有任何伪代码或某些代码可用。
$ \ endgroup $
– Saravanabalagi Ramachandran
18年6月23日在13:17