Основы обработки изображений

Пару дней назад я столкнулся с проектом, в котором требовалось убирать белый фон у скетчей, когда они помещаются в определенную папку. Все это происходило в аппаратном сканере.

Вот пример скетча:

Первым шагом является установка зависимостей для этого проекта, которые перечислены ниже. Мы также будем использовать Python 3.7.

opencv_python==4.1.0.25
pip install opencv-python
numpy==1.16.4
pip install numpy

После этого мы начнем с импорта всех необходимых модулей для проекта:

import cv2
import os
import string
import random
from os import listdir
from os.path import isfile, join, splitext
import time
import sys
import numpy as np
import argparse

Затем мы создаем три разные переменные: имя папки для просмотра, имя папки, в которой изображения будут находиться после обработки, и время опроса при просмотре папки (то есть, как часто он проверяет наличие изменений в папке - одна секунда в нашем случае)

watch_folder = ‘toprocess’
processed_folder = ‘processed’
poll_time = 1

Папки «toprocess» и «processing» пока будут находиться в одном каталоге нашего скрипта python.

Затем мы перейдем к нашей основной функции, которая будет следить за нашим каталогом «toprocess» и, если произойдут какие-либо изменения, обработает изображение, помещенное в эту папку.

before = dict([(f, None) for f in os.listdir(watch_folder)])
while 1:
  time.sleep(poll_time)
  after = dict([(f, None) for f in os.listdir(watch_folder)])
  added = [f for f in after if not f in before]
  removed = [f for f in before if not f in after]
  if added:
    print(“Added “, “, “.join(added))
  if added[0] is not None:
    processImage(added[0])
  if removed:
    print(“Removed “, “, “.join(removed))
  before = after

Этот код работает в бесконечном цикле, пока сценарий не будет убит. При запуске он сохраняет файлы в каталоге внутри словаря с именем «до». Далее шаги в бесконечном цикле разбиты ниже в псевдокоде:

  • Сон в течение назначенного poll_time (1 секунда).
  • Сохраните информацию о файле в каталоге в словаре с именем after.
  • Сохраните то, что было добавлено, сравнив, что находится IN после и НЕ до (f для f после if не встречал раньше).
  • Сохраните то, что было удалено, сравнив, что было В до и НЕ после.
  • Проверьте последний добавленный элемент (добавлен [0]), если он есть, затем вызовите функцию, которую мы обсудим в части под названием processImage в файле.
  • В случае удаления просто сообщите пользователю, распечатав некоторую информацию.
  • Наконец, обновите до и после, чтобы получить последние файлы в каталоге.

Теперь о функции processImage, которая является сердцем программы. Именно здесь происходит волшебство удаления фона OpenCV. Код поясняется комментариями ниже (требуются базовые знания OpenCV):

def processImage(fileName):
  # Load in the image using the typical imread function using our watch_folder path, and the fileName passed in, then set the final output image to our current image for now
  image = cv2.imread(watch_folder + ‘/’ + fileName)
  output = image
  # Set thresholds. Here, we are using the Hue, Saturation, Value color space model. We will be using these values to decide what values to show in the ranges using a minimum and maximum value. 
THESE VALUES CAN BE PLAYED AROUND FOR DIFFERENT COLORS
  hMin = 29  # Hue minimum
  sMin = 30  # Saturation minimum
  vMin = 0   # Value minimum (Also referred to as brightness)
  hMax = 179 # Hue maximum
  sMax = 255 # Saturation maximum
  vMax = 255 # Value maximum
  # Set the minimum and max HSV values to display in the output image using numpys' array function. We need the numpy array since OpenCVs' inRange function will use those.
  lower = np.array([hMin, sMin, vMin])
  upper = np.array([hMax, sMax, vMax])
  # Create HSV Image and threshold it into the proper range.
  hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # Converting color space from BGR to HSV
  mask = cv2.inRange(hsv, lower, upper) # Create a mask based on the lower and upper range, using the new HSV image
  # Create the output image, using the mask created above. This will perform the removal of all unneeded colors, but will keep a black background.
  output = cv2.bitwise_and(image, image, mask=mask)
  # Add an alpha channel, and update the output image variable
  *_, alpha = cv2.split(output)
  dst = cv2.merge((output, alpha))
  output = dst
  # Resize the image to 512, 512 (This can be put into a variable for more flexibility), and update the output image variable.
  dim = (512, 512)
  output = cv2.resize(output, dim)
  # Generate a random file name using a mini helper function called randomString to write the image data to, and then save it in the processed_folder path, using the generated filename.
  file_name = randomString(5) + ‘.png’
  cv2.imwrite(processed_folder + ‘/’ + file_name, output)

Это была довольно простая функция, и она выполняет свою работу правильно. Опять же, игра с порогами может дать еще лучшие результаты. И последнее, что нам нужно обсудить, - это вспомогательная мини-функция, которая генерирует случайную строку для имени файла.

def randomString(length):
  letters = string.ascii_lowercase
  return ‘’.join(random.choice(letters) for i in range(length))

Это простая функция. Он получает буквы, используя «строковую» библиотеку, а затем присоединяется к случайному выбору символов в зависимости от длины, которую вы передали. При передаче длины 5 будет сгенерирована строка из 5 символов.

Результат обработки показан ниже.

Конечно, этот результат можно улучшить, поэкспериментировав со значениями и получив более качественные отсканированные изображения.

Спасибо, что придерживались конца этой статьи. Надеюсь, это поможет вам в ваших будущих проектах.