OpenCV - обнаружение компонентов определенной ширины - PullRequest
2 голосов
/ 27 мая 2020

У меня есть образ с обнаруженными компонентами. Исходя из этого, мне нужно обнаружить компоненты, которые образуют «ломаную линию» определенной ширины (белый и красный на изображении ниже).

Какой алгоритм лучше всего подходит для этого в OpenCV? Я попытался разделить все компоненты один за другим и использовать морфологические операции, но это было довольно медленно и не совсем точно.

Примечание: изображение ниже уменьшено. Исходное изображение имеет разрешение 8K, а толщина границы составляет прибл. 30-40 пикселей.

enter image description here

Ответы [ 3 ]

2 голосов
/ 27 мая 2020

Я упомянул эту концепцию в своем комментарии. Один из неэлегантных способов добиться этого может быть примерно таким:

_, ctrs, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
out = np.zeros(img.shape[:2], dtype="uint8")
epsilon = 1.0
desired_width = 30.0

for i in range(len(ctrs)):
    if(hierarchy[0][i][3] != -1):
        continue
    a = cv2.contourArea(ctrs[i])
    p = cv2.arcLength(ctrs[i], True)
    print(a, p)
    if a != 0 and p != 0 and abs(a/((p-2*desired_width)/2) - desired_width) < epsilon:
        cv2.drawContours(out, [ctrs[i]], -1, 255, -1)

Некоторые параметры могут нуждаться в настройке в зависимости от того, как opencv вычисляет площадь и периметр.

EDIT: добавление тестового изображения, которое имеет 4 волнистые линии шириной 14-16 пикселей. Конечно, это слишком c по сравнению с изображениями, с которыми вы имеете дело.

enter image description here

2 голосов
/ 29 мая 2020

Мне нравится ваш вопрос - это что-то вроде гранулометрии линий, а не зерен.

Мой подход состоит в том, чтобы найти уникальные цвета на вашем изображении, а затем для каждого цвета:

  • выделить этот цвет как белый на черном
  • многократно размывается на 3 пикселя, пока ничего не остается

Обратите внимание, что 20-30% кода ниже предназначены только для отладки и объяснения , а также то, что его можно ускорить с помощью множественной обработки и небольшой настройки.


#!/usr/bin/env python3

import cv2
import numpy as np
from skimage.morphology import medial_axis, erosion, disk

def getColoursAndCounts(im):
   """Returns list of unique colours in an image and their counts."""

   # Make a single 24-bit number for each pixel - it's faster
   f = np.dot(im.astype(np.uint32), [1,256,65536]) 
   # Count unique colours in image and how often they occur
   colours, counts = np.unique(f, return_counts=1)
   # Convert found colours back from 24-bit number to BGR
   return np.dstack((colours&255,(colours>>8)&255,colours>>16)).reshape((-1,3)), counts

if __name__ == "__main__":

   # Load image and get colours present and their counts
   im = cv2.imread('classes_fs.png',cv2.IMREAD_COLOR)
   colours, counts = getColoursAndCounts(im)

   # Iterate over unique colours/classes - this could easily be multi-processed
   for index, colour in enumerate(colours):
      b, g, r = colour
      count = counts[index]
      print(f'DEBUG: Processing class {index}, colour ({b},{g},{r}), area {count}')

      # Generate this class in white on a black background for processing
      m = np.where(np.all(im==[colour], axis=-1), 255, 0).astype(np.uint8)
      # Create debug image - can be omitted
      cv2.imwrite(f'class-{index}.png', m)

      # DEBUG only - show progression of erosion
      out = m.copy()

      # You could trim the excess black around the shape here to speed up morphology

      # Erode, repeatedly with disk of radius 3 to determine line width
      radius = 3
      selem = disk(radius)
      for j in range(1,7):
         # Erode again, see what's left
         m  = erosion(m,selem)
         c = cv2.countNonZero(m)
         percRem = int(c*100/count)
         print(f'   Iteration: {j}, nonZero: {c}, %remaining: {percRem}')
         # DEBUG only
         out = np.hstack((out, m))

         if c==0:
            break
      # DEBUG only
      cv2.imwrite(f'erosion-{index}.png', out)

Итак, 35 уникальных цветов в вашем изображении порождают эти классы, когда-то изолированные:

enter image description here

Вот результат:

DEBUG: Processing class 0, colour (0,0,0), area 629800
   Iteration: 1, nonZero: 390312, %remaining: 61
   Iteration: 2, nonZero: 206418, %remaining: 32
   Iteration: 3, nonZero: 123643, %remaining: 19
   Iteration: 4, nonZero: 73434, %remaining: 11
   Iteration: 5, nonZero: 40059, %remaining: 6
   Iteration: 6, nonZero: 21975, %remaining: 3
DEBUG: Processing class 1, colour (10,14,0), area 5700
   Iteration: 1, nonZero: 2024, %remaining: 35
   Iteration: 2, nonZero: 38, %remaining: 0
   Iteration: 3, nonZero: 3, %remaining: 0
   Iteration: 4, nonZero: 0, %remaining: 0
...
...
DEBUG: Processing class 22, colour (174,41,180), area 3600
   Iteration: 1, nonZero: 1501, %remaining: 41
   Iteration: 2, nonZero: 222, %remaining: 6
   Iteration: 3, nonZero: 17, %remaining: 0
   Iteration: 4, nonZero: 0, %remaining: 0
DEBUG: Processing class 23, colour (241,11,185), area 200
   Iteration: 1, nonZero: 56, %remaining: 28
   Iteration: 2, nonZero: 0, %remaining: 0
DEBUG: Processing class 24, colour (247,23,185), area 44800
   Iteration: 1, nonZero: 38666, %remaining: 86
   Iteration: 2, nonZero: 32982, %remaining: 73
   Iteration: 3, nonZero: 27904, %remaining: 62
   Iteration: 4, nonZero: 23364, %remaining: 52
   Iteration: 5, nonZero: 19267, %remaining: 43
   Iteration: 6, nonZero: 15718, %remaining: 35
DEBUG: Processing class 25, colour (165,142,185), area 33800
   Iteration: 1, nonZero: 30506, %remaining: 90
   Iteration: 2, nonZero: 27554, %remaining: 81
   Iteration: 3, nonZero: 24970, %remaining: 73
   Iteration: 4, nonZero: 22603, %remaining: 66
   Iteration: 5, nonZero: 20351, %remaining: 60
   Iteration: 6, nonZero: 18206, %remaining: 53
DEBUG: Processing class 26, colour (26,147,198), area 2100
   Iteration: 1, nonZero: 913, %remaining: 43
   Iteration: 2, nonZero: 152, %remaining: 7
   Iteration: 3, nonZero: 12, %remaining: 0
   Iteration: 4, nonZero: 0, %remaining: 0
DEBUG: Processing class 27, colour (190,39,199), area 18500
   Iteration: 1, nonZero: 6265, %remaining: 33
   Iteration: 2, nonZero: 0, %remaining: 0
DEBUG: Processing class 28, colour (149,210,201), area 2200
   Iteration: 1, nonZero: 598, %remaining: 27
   Iteration: 2, nonZero: 0, %remaining: 0
DEBUG: Processing class 29, colour (188,169,216), area 10700
   Iteration: 1, nonZero: 9643, %remaining: 90
   Iteration: 2, nonZero: 8664, %remaining: 80
   Iteration: 3, nonZero: 7763, %remaining: 72
   Iteration: 4, nonZero: 6932, %remaining: 64
   Iteration: 5, nonZero: 6169, %remaining: 57
   Iteration: 6, nonZero: 5460, %remaining: 51
DEBUG: Processing class 30, colour (100,126,217), area 5624300
   Iteration: 1, nonZero: 5565713, %remaining: 98
   Iteration: 2, nonZero: 5511150, %remaining: 97
   Iteration: 3, nonZero: 5464286, %remaining: 97
   Iteration: 4, nonZero: 5420125, %remaining: 96
   Iteration: 5, nonZero: 5377851, %remaining: 95
   Iteration: 6, nonZero: 5337091, %remaining: 94
DEBUG: Processing class 31, colour (68,238,237), area 2100
   Iteration: 1, nonZero: 1446, %remaining: 68
   Iteration: 2, nonZero: 922, %remaining: 43
   Iteration: 3, nonZero: 589, %remaining: 28
   Iteration: 4, nonZero: 336, %remaining: 16
   Iteration: 5, nonZero: 151, %remaining: 7
   Iteration: 6, nonZero: 38, %remaining: 1
DEBUG: Processing class 32, colour (131,228,240), area 4000
   Iteration: 1, nonZero: 3358, %remaining: 83
   Iteration: 2, nonZero: 2788, %remaining: 69
   Iteration: 3, nonZero: 2290, %remaining: 57
   Iteration: 4, nonZero: 1866, %remaining: 46
   Iteration: 5, nonZero: 1490, %remaining: 37
   Iteration: 6, nonZero: 1154, %remaining: 28
DEBUG: Processing class 33, colour (0,0,255), area 8500
   Iteration: 1, nonZero: 6046, %remaining: 71
   Iteration: 2, nonZero: 3906, %remaining: 45
   Iteration: 3, nonZero: 2350, %remaining: 27
   Iteration: 4, nonZero: 1119, %remaining: 13
   Iteration: 5, nonZero: 194, %remaining: 2
   Iteration: 6, nonZero: 18, %remaining: 0
DEBUG: Processing class 34, colour (255,255,255), area 154300
   Iteration: 1, nonZero: 117393, %remaining: 76
   Iteration: 2, nonZero: 82930, %remaining: 53
   Iteration: 3, nonZero: 51625, %remaining: 33
   Iteration: 4, nonZero: 24842, %remaining: 16
   Iteration: 5, nonZero: 6967, %remaining: 4
   Iteration: 6, nonZero: 2020, %remaining: 1

Если мы посмотрим на класс 34 - тот, который вас интересует. Последовательные эрозии выглядят вот так - вы можете увидеть, как фигура полностью исчезает с радиусом около 15 пикселей, что соответствует потере 15 пикселей слева и 15 пикселей справа от вашей формы шириной 30 пикселей:

enter image description here

Если строить график процент пикселей, оставшихся после каждой последовательной эрозии, вы можете легко увидеть разницу между классом 34, где он становится равным нулю после 5-6 эрозий по 3 пикселя каждый (т.е. 15-18 пикселей), и классом 25, где этого не происходит:

enter image description here

Примечания :

Для всех, кто хочет запустить мой код, обратите внимание, что я увеличил масштаб ввода изображение (передискретизация ближайшего соседа) до 10-кратного его текущего размера с помощью ImageMagick :

magick classes.png -scale 1000%x classes_fs.png
1 голос
/ 27 мая 2020

Вы можете попробовать следующее:

  1. Преобразуйте изображение в двухцветное. Объекты белые, границы черные.
  2. Эрозия всех объектов на 15-20 пикселей. Получаем маркер.
  3. Морфологическая реконструкция исходного изображения маркером. Вы получаете изображение без узких линий.
  4. Побитовое исключающее ИЛИ абзацы 1 и 3.
...