Python 中的图像注视点

Image foveation in Python

我希望能够在 Python 中以焦点位于图像中心的方式对图像进行注视。我的输入图像可以表示为二维 Numpy 数组。我想在中心获得高分辨率的输出图像,但在两侧模糊。为此,我找到了一个名为 logplar_interp 的 OpenCV 函数,但它似乎不存在于 OpenCV 的 Python 包装中。我感谢任何帮助。

注视点图像的示例如下所示(取自Wikipedia):

焦点是右上角的墓碑,而当您远离焦点时,其余像素会逐渐变得模糊。

这是我使用 OpenCV Python 重新创建的尝试。这是一个相当 hack-ish 的解决方案,计算量有点大,但它肯定能完成工作。

首先,创建一个遮罩,其中 0 的像素对应于您希望保持高分辨率的像素和 1[=48= 的像素] 对应于您要模糊的那些像素。为简单起见,我将创建一个暗像素圆圈来定义高分辨率像素。

有了这个面具,我可以建议使用这个面具上的 distance transform 来完成这项工作的一个工具。对于二进制掩码中的每个点,距离变换中对应的输出点是从该点到最近的零像素的距离。因此,当您冒险远离蒙版中的零像素时,距离会越大。

因此,您离此蒙版中的零像素越远,应用的模糊就越多。使用这个想法,我简单地写了一个遍历图像的循环,并在每个点创建一个模糊蒙版——无论是平均蒙版还是高斯蒙版或任何与之相关的蒙版——它与距离变换中的距离成正比,并用那个模糊这个点模糊蒙版。此蒙版中任何为零的值都不应应用任何模糊。对于掩码中的所有其他点,我们使用掩码中的值来指导我们收集以该点为中心的像素邻域并执行模糊。距离越大,像素邻域越大,模糊越强。

为了简化事情,我将使用平均蒙版。具体来说,对于距离变换中的每个值,此掩码的大小将为 M x M,其中 M 为:

M = d / S

d 是距离变换的距离值,S 是缩小 d 值的比例因子,以便平均化更可行。这是因为当您远离零像素时,距离变换会变得非常大,因此比例因子使平均更加真实。正式地,对于我们输出中的每个像素,我们收集 M x M 个像素的邻域,得到一个平均值并将其设置为我们的输出。

我们需要牢记的一个复杂问题是,当我们收集邻域中心位于图像边界的像素时,我们需要确保收集图像边界内的像素,以便任何超出图像的位置,我们都会跳过。

现在是展示一些结果的时候了。作为参考,我使用了 Camera Man 图像,这是一个标准的测试图像,非常受欢迎。此处显示:

我还要将掩码设置为位于第 70 行和第 100 列的半径为 25 的圆。事不宜迟,下面是完整注释的代码。我会让你自己解析评论。

import cv2 # Import relevant libraries
import cv
import numpy as np

img = cv2.imread('cameraman.png', 0) # Read in image

height = img.shape[0] # Get the dimensions
width = img.shape[1]

# Define mask
mask = 255*np.ones(img.shape, dtype='uint8')

# Draw circle at x = 100, y = 70 of radius 25 and fill this in with 0
cv2.circle(mask, (100, 70), 25, 0, -1)    

# Apply distance transform to mask
out = cv2.distanceTransform(mask, cv.CV_DIST_L2, 3)

# Define scale factor
scale_factor = 10

# Create output image that is the same as the original
filtered = img.copy() 

# Create floating point copy for precision
img_float = img.copy().astype('float')

# Number of channels
if len(img_float.shape) == 3:
  num_chan = img_float.shape[2]
else:
  # If there is a single channel, make the images 3D with a singleton
  # dimension to allow for loop to work properly
  num_chan = 1
  img_float = img_float[:,:,None]
  filtered = filtered[:,:,None]

# For each pixel in the input...
for y in range(height):
  for x in range(width):

    # If distance transform is 0, skip
    if out[y,x] == 0.0:
      continue

    # Calculate M = d / S
    mask_val = np.ceil(out[y,x] / scale_factor)

    # If M is too small, set the mask size to the smallest possible value
    if mask_val <= 3:
      mask_val = 3

    # Get beginning and ending x and y coordinates for neighbourhood
    # and ensure they are within bounds
    beginx = x-int(mask_val/2)
    if beginx < 0:
      beginx = 0

    beginy = y-int(mask_val/2)
    if beginy < 0:
      beginy = 0

    endx = x+int(mask_val/2)
    if endx >= width:
      endx = width-1

    endy = y+int(mask_val/2)
    if endy >= height:
      endy = height-1

    # Get the coordinates of where we need to grab pixels
    xvals = np.arange(beginx, endx+1)
    yvals = np.arange(beginy, endy+1)
    (col_neigh,row_neigh) = np.meshgrid(xvals, yvals)
    col_neigh = col_neigh.astype('int')
    row_neigh = row_neigh.astype('int')

    # Get the pixels now
    # For each channel, do the foveation
    for ii in range(num_chan):
      chan = img_float[:,:,ii]
      pix = chan[row_neigh, col_neigh].ravel()

      # Calculate the average and set it to be the output
      filtered[y,x,ii] = int(np.mean(pix))

# Remove singleton dimension if required for display and saving
if num_chan == 1:
  filtered = filtered[:,:,0]

# Show the image
cv2.imshow('Output', filtered)
cv2.waitKey(0)
cv2.destroyAllWindows()

我得到的输出是: