簡體   English   中英

如何改進 Python OpenCV 中的可變鏡頭模糊算法?

[英]How to improve a variable lens blur algorithm in Python OpenCV?

我想模仿廉價相機鏡頭(如Holga )的模糊效果。
靠近照片中心的模糊非常弱。
它在靠近彎道時變得更加果斷。

我編寫了代碼,它通常可以工作。

輸入圖像:

輸入圖像

結果圖片:

結果圖片 .

但我覺得它可以做得更好更快。
我發現了一個類似的問題,但仍然沒有答案。

如何提高算法速度並避免像素迭代?

更新:
它與具有恆定內核大小的標准高斯或 2D 過濾器模糊不同。

import cv2
import numpy as np
import requests
from tqdm import tqdm
import warnings
warnings.filterwarnings("ignore")

def blur(img=None, blur_radius=None, test=False):
    # test image loading
    if img is None:
        test=True
        print('test mode ON')
        print('loading image...')
        url = r'http://www.lenna.org/lena_std.tif'
        resp = requests.get(url, stream=True).raw
        img = np.asarray(bytearray(resp.read()), dtype="uint8")
        img = cv2.imdecode(img, cv2.IMREAD_COLOR)
        cv2.imwrite('img_input.png', img)
        print('image loaded')

    # channels splitting
    img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(img_lab)
    if test:
        cv2.imwrite('l_channel.png', l)
        print('l channel saved')

    # make blur map 
    height, width = l.shape[:2]
    center = np.array([height/2, width/2])
    diag = ((height / 2) ** 2 + (width / 2) ** 2) ** 0.5
    blur_map = np.linalg.norm(
        np.indices(img.shape[:2]) - center[:,None,None] + 0.5,
        axis = 0
    )

    if blur_radius is None:
        blur_radius = int(max(height, width) * 0.03)

    blur_map = blur_map / diag 
    blur_map = blur_map * blur_radius
    if test:
        blur_map_norm = cv2.normalize(blur_map, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_32F)
        cv2.imwrite('blur_map.png', blur_map_norm)
        print('blur map saved')

    # very inefficient blur algorithm!!!
    l_blur = np.copy(l)
    for x in tqdm(range(width)):
        for y in range(height):
            kernel_size = int(blur_map[y, x])
       
            if kernel_size == 0:
                l_blur[y, x] = l[y, x]
                continue
            
            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
            cut = l[
                max(0, y - kernel_size):min(height, y + kernel_size),
                max(0, x - kernel_size):min(width, x + kernel_size)
            ]

            if cut.shape == kernel.shape:
                cut = (cut * kernel).mean()
            else:
                cut = cut.mean()

            l_blur[y, x] = cut
    if test: cv2.imwrite('l_blur.png', l_blur); print('l_blur saved')
    if test: print('done')
    return l_blur

blur() 

實現每個像素的內核不同的過濾器的唯一方法是為每個像素創建內核並在循環中應用它,就像 OP 的代碼一樣。 傅里葉變換不適用於這種情況。 Python 是一種非常慢的語言,用編譯語言實現的相同算法會快得多。 除非在每個像素處創建內核的方式中有一些預定義的結構,否則無法降低算法的復雜性。

例如,可以基於積分圖像計算具有方形內核的均勻濾波器(通常稱為“盒”濾波器),每個像素僅使用 4 次加法。 此實現應該能夠在每個像素處選擇不同的內核大小,而無需任何額外成本。

DIPlib 有一個自適應高斯濾波器的實現[免責聲明:我是 DIPlib 的作者,但我沒有實現這個功能]。 這是文檔 此過濾器應用高斯過濾器,但高斯核在每個像素處的縮放和旋轉方式不同。

鏡頭模糊不是高斯模糊,但在大多數情況下很難用肉眼看出差異; 只有當有一個非常小的點具有高對比度時,差異才重要。

OP的案例將執行如下:

import diplib as dip

img = dip.ImageRead('examples/trui.ics')

blur_map = dip.CreateRadiusSquareCoordinate(img.Sizes())
blur_map /= dip.Maximum(blur_map)

img_blur = dip.AdaptiveGauss(img, [0, blur_map], sigmas=[5])

(這里的blur_map定義不同,我選擇了到中心距離的二次函數,因為我覺得它看起來真的很好;使用dip.CreateRadiusCoordinate()來重現 OP 的地圖)。

輸入圖像 輸出圖像

我選擇了 5 的最大模糊(這是高斯的 sigma,以像素為單位,而不是內核的足跡),並且blur_map在這里用中間的 0 和角落的 1 之間的因子縮放這個 sigma圖片。

另一個有趣的效果如下,隨着圖像中間每個圓的切向模糊增加,徑向模糊很少:

angle_map = dip.CreatePhiCoordinate(img.Sizes())
img_blur = dip.AdaptiveGauss(img, [angle_map, blur_map], sigmas=[8,1])

輸出圖像

這是在 Python/OpenCV 中應用(均勻、不變)鏡頭散焦模糊的一種方法,方法是將圖像和濾波器都轉換到傅立葉(頻率)域。

  • 讀取輸入
  • 將輸入的 dft 轉換為傅立葉域
  • 在黑色背景上繪制一個白色實心圓圈,輸入大小作為掩碼(過濾器內核)。 這是空間域中的散焦核,即圓形矩形函數。
  • 稍微模糊圓圈以消除邊緣的鋸齒
  • 滾動蒙版,使中心位於原點(左上角)並標准化,使值的總和 = 1
  • 將掩碼的 dft 轉換為傅立葉域,其中其幅度分布是一個 jinx 函數。
  • 將兩個 dft 圖像相乘以應用模糊
  • 取乘積的 idft 變換回空間域
  • 獲取乘積的實部和虛部的大小,裁剪並轉換為uint8作為結果
  • 保存結果

輸入:

在此處輸入圖像描述

import numpy as np
import cv2

# read input and convert to grayscale
img = cv2.imread('lena_512_gray.png', cv2.IMREAD_GRAYSCALE)

# do dft saving as complex output
dft_img = np.fft.fft2(img, axes=(0,1))

# create circle mask
radius = 32
mask = np.zeros_like(img)
cy = mask.shape[0] // 2
cx = mask.shape[1] // 2
cv2.circle(mask, (cx,cy), radius, 255, -1)[0]

# blur the mask slightly to antialias
mask = cv2.GaussianBlur(mask, (3,3), 0)

# roll the mask so that center is at origin and normalize to sum=1
mask_roll = np.roll(mask, (256,256), axis=(0,1))
mask_norm = mask_roll / mask_roll.sum() 

# take dft of mask
dft_mask_norm = np.fft.fft2(mask_norm, axes=(0,1))

# apply dft_mask to dft_img
dft_shift_product = np.multiply(dft_img, dft_mask_norm)

# do idft saving as complex output
img_filtered = np.fft.ifft2(dft_shift_product, axes=(0,1))

# combine complex real and imaginary components to form (the magnitude for) the original image again
img_filtered = np.abs(img_filtered).clip(0,255).astype(np.uint8)

cv2.imshow("ORIGINAL", img)
cv2.imshow("MASK", mask)
cv2.imshow("FILTERED DFT/IFT ROUND TRIP", img_filtered)
cv2.waitKey(0)
cv2.destroyAllWindows()

# write result to disk
cv2.imwrite("lena_512_gray_mask.png", mask)
cv2.imwrite("lena_dft_numpy_lowpass_filtered_rad32.jpg", img_filtered)

掩碼 - 空間域中的過濾器內核:

在此處輸入圖像描述

圓半徑 = 4 的結果:

在此處輸入圖像描述

圓半徑 = 8 的結果:

在此處輸入圖像描述

圓半徑 = 16 的結果:

在此處輸入圖像描述

圓半徑=32 的結果

在此處輸入圖像描述

添加

將 OpenCV 用於 dft 等而不是 Numpy,以上變為:

import numpy as np
import cv2

# read input and convert to grayscale
img = cv2.imread('lena_512_gray.png', cv2.IMREAD_GRAYSCALE)

# do dft saving as complex output
dft_img = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)

# create circle mask
radius = 32
mask = np.zeros_like(img)
cy = mask.shape[0] // 2
cx = mask.shape[1] // 2
cv2.circle(mask, (cx,cy), radius, 255, -1)[0]

# blur the mask slightly to antialias
mask = cv2.GaussianBlur(mask, (3,3), 0)

# roll the mask so that center is at origin and normalize to sum=1
mask_roll = np.roll(mask, (256,256), axis=(0,1))
mask_norm = mask_roll / mask_roll.sum() 

# take dft of mask
dft_mask_norm = cv2.dft(np.float32(mask_norm), flags = cv2.DFT_COMPLEX_OUTPUT)

# apply dft_mask to dft_img
dft_product = cv2.mulSpectrums(dft_img, dft_mask_norm, 0)

# do idft saving as complex output, then clip and convert to uint8
img_filtered = cv2.idft(dft_product, flags=cv2.DFT_SCALE+cv2.DFT_REAL_OUTPUT)
img_filtered = img_filtered.clip(0,255).astype(np.uint8)

cv2.imshow("ORIGINAL", img)
cv2.imshow("MASK", mask)
cv2.imshow("FILTERED DFT/IFT ROUND TRIP", img_filtered)
cv2.waitKey(0)
cv2.destroyAllWindows()

# write result to disk
cv2.imwrite("lena_512_gray_mask.png", mask)
cv2.imwrite("lena_dft_opencv_defocus_rad32.jpg", img_filtered)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM