Привет, Хабр! Это продолжение туториала по библиотеке opencv в python. Для тех кто не читал первую и вторую части, сюда: Часть 1 и Часть 2, а всем остальным приятного чтения!
Введение
В данной части мы рассмотрим арифметику изображений, разделение и слияние каналов, различные методы размытия.
Арифметика изображений
Надеюсь, что все знают такие арифметические операции как
сложение и вычитание, но при работе с изображениями мы не должны
забывать о типе данных.
К примеру, у нас есть RGB изображение, пиксели которого попадают в
диапазон [0,255]. Итак, что же произойдёт, если мы попытаемся к
пикселю с интенсивностью 250 прибавить 30 или от 70 отнять 100?
Если бы мы пользовались стандартными арифметическими правилами, то
получили бы 280 и -30 соответственно. Однако, если мы работаем с
RGB изображениями, где значения пикселей представлены в виде
8-битного целого беззнакового числа, то 280 и -30 не является
допустимыми значениями. Для того, чтобы разобраться, что же
произойдёт, давайте посмотрим на строчки кода ниже:
print("opencv addition: {}".format(cv2.add(np.uint8([250]), np.uint8([30]))))print("opencv subtract: {}".format(cv2.subtract(np.uint8([70]), np.uint8([100]))))print("numpy addition: {}".format(np.uint8([250]) + np.uint8([30])))print("numpy subtract: {}".format(np.uint8([70]) - np.uint8([71])))
Как мы видим, сложение и вычитание можно осуществить с помощью функций opencv add и subtract соответственно, а также с помощью numpy. И результаты будут отличаться:
opencv addition: 255opencv subtract: 0numpy addition: 24numpy subtract: 255
OpenCV выполняет обрезку и гарантирует, что значения пикселей никогда не выйдут за пределы диапазона [0,255]. В numpy же всё происходит немного иначе. Представьте себе обычные настенные часы, где вместо 60 находится 255. Получается, что после достижение 255 следующим числом будет идти 0, а когда мы отнимаем от меньшего числа большее, то после 0 ( против часовой стрелки) будет идти 255.
Разбиение и слияние каналов
Как мы знаем, RGB изображение состоит из красной, зелёной и синих компонент. И что, если мы захотим разделить изображение на соответствующие компоненты? Для этого в opencv есть специальная функция split():
image = cv2.imread('rectangles.png')b, g, r = cv2.split(image)cv2.imshow('blue', b)cv2.imshow('green', g)cv2.imshow('red', r)
Сначала мы загружаем изображение. Для наглядности работы данной функции я взял следующее изображение:
Затем разделяем изображение на три канала и показываем каждый канал по отдельности. В результате выполнения данной функции отобразится три изображения в оттенках серого:
Как мы видим, для каждого изображения каждого канала только прямоугольник с тем же цветом отображается белым. Вот как будет выглядеть каждая компонента для девушки из предыдущих частей:
Как можно увидеть, красный канал очень светлый. Это происходит
потому, что оттенки красного очень сильно представлены в нашем
изображении. Синий и зелёный каналы, наоборот, очень тёмные. Это
случается потому, что на данном изображении очень мало данных
цветов.
Для того, чтобы объединить каналы воедино, достаточно
воспользоваться функцией merge(), которая принимает значения
каналов:
merge_image = cv2.merge([g,b,r])cv2.imshow('merge_image', merge_image)cv2.imshow('original', image)cv2.waitKey(0)
Таким образом, мы получаем такое же изображение как оригинальное, за исключением того, что я поменял местами синий с зелёным каналом.
Размытие
Размытие это когда более резкие области на изображении теряют свою детализацию, в результате чего изображение становится менее чётким. В opencv имеются следующие основные методы размытия: averaging(усреднённое), gaussian(гауссово) и median(медианное).
Averaging
Данный фильтр делает операцию свёртки на изображении с неким ядром, где свёртка это вычисление нового значения пикселя, при котором учитываются значения соседних пикселей. Ядро свёртки это квадратная матрица, где пиксель в центре этой матрицы затем устанавливается как среднее значение всех других пикселей, окружающих его. Для того, чтобы воспользоваться данным размытием достаточно вызвать метод blur(), который принимает изображение и кортеж, с указанием размера ядра:
def averaging_blurring(): image = cv2.imread('girl.jpg') img_blur_3 = cv2.blur(image, (3, 3)) img_blur_7 = cv2.blur(image, (7, 7)) img_blur_11 = cv2.blur(image, (11, 11))
Чем больше размер ядра, тем более размытым будет становиться изображение:
Gaussian
Гауссово размытие похоже на предыдущее размытие, за исключением того, что вместо простого среднего мы теперь используем взвешенное среднее, где соседние пиксели, которые ближе к центральному пикселю, вносят больший вклад в среднее. Конечным результатом является то, что наше изображение размыто более естественно:
Это размытие реализуется в opencv с помощью функции GaussianBlur(), которая принимает первые два аргумента такие же как и предыдущая функция, а третьим аргументом указываем стандартное отклонение ядра Гаусса. Установив это значение в 0, мы тем самым говорим opencv автоматически вычислять его, в зависимости от размера нашего ядра:
def gaussian_blurring(): image = cv2.imread('girl.jpg') img_blur_3 = cv2.GaussianBlur(image, (3, 3), 0) img_blur_7 = cv2.GaussianBlur(image, (7, 7), 0) img_blur_11 = cv2.GaussianBlur(image, (11, 11), 0)
Median
В медианном размытии центральный пиксель изображения заменяется медианой всех пикселей в области ядра, в результате чего это размытие наиболее эффективно при удалении шума в стиле соли. Для того, чтобы применить данный вид размытия, необходимо вызвать функцию medianBlur() и передать туда два параметра: изображение и размер ядра:
def median_blurring(): image = cv2.imread('girl.jpg') img_blur_3 = cv2.medianBlur(image, 3) img_blur_7 = cv2.medianBlur(image, 7) img_blur_11 = cv2.medianBlur(image, 11)
В результате у нас получится следующее:
На этом данная часть подошла к концу. Код, как всегда, доступен на github. До скорой встречи:)