Свёрточные операции
Contents
Свёрточные операции#
Свёрточные операции позволяют делать много полезных преобразований над изображениями.
import os
import numpy as np
from matplotlib import pyplot as plt
import cv2
image_folder = os.path.join("..", "..", "_static", "lecture_specific", "cv")
def show_image(ax, image, title=None, cmap=None):
"""
Вывести изображение в указанных осях.
"""
ax.imshow(image, cmap=cmap)
ax.set_title(title)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
path = os.path.join(image_folder, "coins.jpg")
image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
fig, ax = plt.subplots(figsize=(5, 5), layout="tight")
show_image(ax, image, cmap="gray")
Размытие#
Например, сделав свертку изображения с ядром
мы получим размытие.
Ниже приводится результат размытия при \(n\) и \(m\) равным 5 пикселям.
blurred_5 = cv2.blur(image, (5, 5))
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], image, title="image", cmap="gray")
show_image(axs[1], blurred_5, title="blur", cmap="gray")
Чем больше ядро, тем сильнее размытие.
blurred_10 = cv2.blur(image, (10, 10))
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], blurred_5, title="5x5", cmap="gray")
show_image(axs[1], blurred_10, title="10x10", cmap="gray")
Чаще для размытия применяют ядро Гаусса, которое имеет колоколообразную форму
Чем больше дисперсия \(\sigma\), тем сильнее размытие.
Получить одномерное ядро Гаусса можно методом cv2.getGaussianKernel. Двумерное ядро можно получить внешним произведением одномерных из-за свойств свертки. Отобразим как выглядят ядра Гаусса при разных значениях дисперсии \(\sigma\).
kernels = [cv2.getGaussianKernel(21, i, cv2.CV_32F) for i in range(1, 21, 3)]
kernels2D = [np.outer(kernel, kernel) for kernel in kernels]
fig, axs = plt.subplots(figsize=(10, 5), ncols=len(kernels2D), layout="tight")
for i, kernel in enumerate(kernels2D):
show_image(axs[i], kernel, title=f"$\sigma={1 + 3*i}$", cmap="gray")
Нет необходимости в явном виде создавать ядро Гаусса, чтобы применить размытие. Достаточно применить функцию cv2.GaussianBlur.
gauss_blurred_1 = cv2.GaussianBlur(image, (21, 21), 1)
gauss_blurred_19 = cv2.GaussianBlur(image, (21, 21), 19)
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], gauss_blurred_1, title="$\sigma=1$", cmap="gray")
show_image(axs[1], gauss_blurred_19, title="$\sigma=19$", cmap="gray")
Размытие Гаусса часто применяют для подавления шумов.
path = os.path.join(image_folder, "OpenCV.png")
openCV = cv2.imread(path) / 255.
noised = openCV + np.random.normal(0, 0.1, size= openCV.shape)
blurred = cv2.GaussianBlur(noised, (5, 5), 2)
fig, axs = plt.subplots(figsize=(15, 5), ncols=3, layout="tight")
show_image(axs[0], openCV, title="$\sigma=1$", cmap="gray")
show_image(axs[1], noised, title="$\sigma=19$", cmap="gray")
show_image(axs[2], blurred, title="$\sigma=19$", cmap="gray")
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Повышение резкости#
Ядро
может быть использовано для повышение резкости изображения.
Метод cv2.filter2D позволяет применять произвольный фильтр к изображению.
kernel = np.array([
[-1,-1,-1],
[-1, 9,-1],
[-1,-1,-1]
])
sharpened_image = cv2.filter2D(gauss_blurred_1, -1, kernel)
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], gauss_blurred_1, title="$\sigma=1$", cmap="gray")
show_image(axs[1], sharpened_image, title="$\sigma=19$", cmap="gray")
Взятие производной#
Соболевское ядро аппроксимирует операцию взятия производной. Так, например, фильтр
соответствует взятию производной по вертикальному направлению. Его транспонирование берет численную производную по горизонтальному направлению.
Рассмотрим задачу поиска контуров объекта на изображении. Т.к. внутри контура и снаружи него располагаются разные цвета (цвет объекта и цвет фона), то в перпендикулярном к контуру направлении наблюдается резкий перепад яркостей, а значит и большое значение производной. Таким образом горизонтальная производная позволяет искать вертикальные контуры, а вертикальная — горизонтальные.
За применение фильтра Собеля в openCV
отвечает метод cv2.Sobel.
path = os.path.join(image_folder, "brickwall.jpg")
brickwall = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
dimage_dx = cv2.Sobel(brickwall, ddepth=-1, dx=1, dy=0, ksize=3)
dimage_dy = cv2.Sobel(brickwall, ddepth=-1, dx=0, dy=1, ksize=3)
fig, axs = plt.subplots(figsize=(11, 5), ncols=3, layout="tight")
show_image(axs[0], brickwall, title="image", cmap="gray")
show_image(axs[1], dimage_dx, title=r"$\frac{d}{dx}$", cmap="gray")
show_image(axs[2], dimage_dy, title=r"$\frac{d}{dy}$", cmap="gray")
Редко бывает так, что все контуры вертикальны и горизонтальны. Чтобы искать контуры с произвольным наклоном, можно взять сумму квадратов горизонтальной и вертикальной производных, что соответствует квадрату нормы градиента изображения.
dimage_dx = cv2.Sobel(image, ddepth=-1, dx=1, dy=0, ksize=3)
dimage_dy = cv2.Sobel(image, ddepth=-1, dx=0, dy=1, ksize=3)
norm = np.sqrt(((dimage_dx/255.)**2 + (dimage_dy/255.)**2))
fig, axs = plt.subplots(figsize=(15, 5), ncols=3, layout="tight")
show_image(axs[0], dimage_dx, title=r"$\frac{d}{dx}$", cmap="gray")
show_image(axs[1], dimage_dy, title=r"$\frac{d}{dy}$", cmap="gray")
show_image(axs[2], norm, title=r"$\sqrt{(\frac{d}{dx} + \frac{d}{dy})^2}$", cmap="gray")
Т.к. взятие производной — операция очень чувствительная к шуму, то обычно изображение предварительно сглаживают размытием Гаусса, а затем только берут производную.
blurred = cv2.GaussianBlur(image, (3, 3), 0)
dimage_dx = cv2.Sobel(blurred, ddepth=-1, dx=1, dy=0, ksize=3)
dimage_dy = cv2.Sobel(blurred, ddepth=-1, dx=0, dy=1, ksize=3)
norm = np.sqrt(((dimage_dx/255.)**2 + (dimage_dy/255.)**2))
fig, axs = plt.subplots(figsize=(15, 5), ncols=3, layout="tight")
show_image(axs[0], dimage_dx, title=r"$\frac{d}{dx}$", cmap="gray")
show_image(axs[1], dimage_dy, title=r"$\frac{d}{dy}$", cmap="gray")
show_image(axs[2], norm, title=r"$\sqrt{(\frac{d}{dx})^2 + (\frac{d}{dy})^2}$", cmap="gray")
Фильтр Лапласа
соответствует второй производной, что в каком-то смысле облегчает поиск контура: в случае первой производной необходимо искать и минимумы и максимумы, в случае второй производной только нули. Метод cv2.Laplacian позволяет применить этот фильтр к изображению.
laplacian = cv2.Laplacian(image, 5)
blurred_laplacian = cv2.Laplacian(blurred, 5)
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], laplacian, title="image -> laplacian", cmap="gray")
show_image(axs[1], blurred_laplacian, title="image -> blur -> laplacian", cmap="gray")
Контуры#
Нелинейный cv2.Canny предназначен специально для поиска контуров.
canny = cv2.Canny(image, 130, 180)
blured = cv2.GaussianBlur(image, (3, 3), 0)
blurred_canny = cv2.Canny(blured, 100, 180)
fig, axs = plt.subplots(figsize=(10, 5), ncols=2, layout="tight")
show_image(axs[0], canny, title="image -> canny", cmap="gray")
show_image(axs[1], blurred_canny, title="image -> blur -> canny", cmap="gray")