Skip to content

图像修图之放大眼睛

放大眼睛分为两步:检测眼睛、放大眼睛。检测眼睛就不写了,那个属于目标检测的范畴。

放大眼睛的思路是源于 Interactive Image Warping 这篇文章。

思路如这张图所示,圆心是眼睛,放大就是从 A 点放大到 B 点。

这里就要谈一个注意点,我第一次实现看了这个图那么简单,很快实现了但结果却很不好,原来有一个注意点:半径上的点放大比例是不一样的,离圆心较近的点放大会强一点,离圆心较远的点放大会弱一点,直到最后到了边缘。

放大的公式为,其中 a 用于控制放大程度,而 rmax 则是规定放大的范围:

上面的公式可能还是抽象,直接看如何调整横坐标的代码:

\[ dis^2 = (x - x_0)^2 + (y - y_0)^2 \]
\[ k = 1 - (1 - \frac{dis^2}{{rmax}^2}) * a \]
\[ newx = x_0 + (x - x_0) * k \]

最后,有经验的人肯定知道,做图像变换往往是遍历目标图片,去推测当前的点是原图的哪个点;而不是遍历原始图片,去推测这个点会变成目标图片上哪个点。

所以回看这个公式,我们要把 x 和 y 当成目标图片的像素点,k 就是缩放比例,根据 k 就能计算出当前像素点从原图哪个点变换来的。所以如果要放大眼睛,那么 k 其实应该是小于 1,是放大比例的倒数,所以 a 要小于 1 去保证 k 小于 1。

此时 rmax 的意思就是我的眼睛最终变换后的半径,而不是变换前的半径。推测出当前像素属于原图哪个点时,如果是小数,要用双线性插值去计算。

代码:

import cv2
import numpy as np
import math

src = cv2.imread('./src.jpg')
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY).astype(float)

def binterp(img, x, y):
    x0, x1 = int(x), int(x) + 1
    y0, y1 = int(y), int(y) + 1

    q00, q01, q10, q11 = img[x0, y0], img[x0, y1], img[x1, y0], img[x1, y1]

    result = 0
    result += q00 * (x1 - x) * (y1 - y)
    result += q10 * (x - x0) * (y1 - y)
    result += q01 * (x1 - x) * (y - y0)
    result += q11 * (x - x0) * (y - y0)

    return result


eyes = [(321, 548)]

radius = 30
scale = 0.2

now = src.copy().astype(float)
for eye in eyes:

    xlo, xhi = int(eye[0]-radius), int(eye[0]+radius)
    ylo, yhi = int(eye[1]-radius), int(eye[1]+radius)

    for i in range(xlo, xhi):
        for j in range(ylo, yhi):
            dx = (i - eye[0])
            dy = (j - eye[1])

            if dx*dx + dy*dy < radius*radius:
                k = 1 - (1 - (dx*dx + dy*dy) / (radius*radius)) * scale

                srcx = eye[0] + dx * k
                srcy = eye[1] + dy * k

                # bilinear interpolation
                now[i, j] = binterp(src, srcx, srcy)

now = now.clip(0, 255).astype(np.uint8)
cv2.imwrite('./test.png', now)

题外话

当然如果觉得这样别扭,非要输入眼睛半径和放大比例,可以换算:

# 原来的眼睛半径,和放大的比例
oldr = 20
scale = 1.2

# newr 表示眼睛最后变换后的半径,也就是最后计算到这个边界就结束
newr = scale * oldr
rmax = newr

# 后面就一样了,根据目标推原图的位置,还是要给一个参数 a,表示放大的变化强度

Comments