OpenCV средняя интенсивность контурных фигур в изображении в оттенках серого

#python #image #numpy #opencv

#python #изображение #numpy #opencv

Вопрос:

Искусственное изображение для тестирования

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

 #!/usr/bin/env python3
import imutils
import cv2
import numpy as np
from scipy.stats import norm

image = cv2.imread("./test_images/test_artificial2.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Noise reduction
blurred = cv2.GaussianBlur(gray, (3, 3), 0)

# thresholding
mean, std=norm.fit(blurred)
thresh_min_value = int(mean   3.6*std)
thresh = cv2.threshold(blurred, thresh_min_value, 255, cv2.THRESH_BINARY)[1]

# find contours in the thresholded image
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

for c in cnts:
    # Moments
    M = cv2.moments(c)
    # Area of contour
    area = M["m00"]

    # Centroid of contour
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])

    # Perimeter
    perimeter = cv2.arcLength(c, True)

    print(f'{area=}')
    print(f'{perimeter=}')

    epsilon = 0.02 * perimeter
    approx = cv2.approxPolyDP(c, epsilon, True)
    print(f'{approx=}')

    # Create blank mask
    mask_contour = np.zeros(gray.shape, np.uint8)

    # Draw contour in the mask
    cv2.drawContours(mask_contour, [approx], -1, (255, 255, 255), -1)

    # This calculates the intensity of the polygon correctly
    intensity = []
    for i in range(0, gray.shape[0]):
        for j in range(0, gray.shape[1]):
            if mask_contour[i][j] == 255:
                intensity.append(gray[i][j])

    print(sum(intensity)/len(intensity))

    # But I would like to speed up the process somehow
    #masked_image = np.where(mask_contour == 255, gray, 0)
    #average_intensity = np.mean(masked_image)
    # print(f'{average_intensity=}')

    cv2.imshow("Image", mask_contour)
    cv2.waitKey(0)
  

Как уже предлагалось в одном комментарии, я мог бы использовать NumPy для вычисления его средней интенсивности, но я не могу заставить его вычислять ее только с пикселями фигур, он добавляет остальные пиксели.

Возможно ли достичь этого каким-либо другим более быстрым методом?

Спасибо.

Комментарии:

1. Пожалуйста, покажите ваше входное изображение без каких-либо контуров или полигонов. 1) Это только вершины многоугольника. 2) Создайте маску для каждого контура или многоугольника (нарисуйте белую заливку на черном фоне). Затем используйте маску, чтобы ограничить площадь пикселей, которые вы используете для получения среднего значения. среднее значение =np.mean(np.где(маска==255))

2. @fmw42 прежде всего, спасибо за ваш ответ, потому что пункт 2 очень полезен, что касается вашего первоначального комментария, предоставленное мной изображение является оригинальным (я создал его искусственно, чтобы улучшить тесты), запуск кода с этим изображением должен показать, что я задал в вопросе

3. @fmw42 не могли бы вы уточнить свой ответ? Я пытаюсь это: mask_contour = np.zeros(gray.shape, np.uint8); cv2.drawContours (mask_contour, [прим], -1, (255, 255, 255), -1); masked_contour = cv2.bitwise_and(серый, grey, маска=mask_contour). На данный момент у меня есть маска с нулями и 255 в области многоугольника, как я могу сказать numpy, чтобы он получил среднее значение серого изображения, используя эту маску? Спасибо.

4. Поскольку они имеют одинаковые размеры, не могли бы вы просто сделать gray[mask_contour==255].mean() ?

5. @imochoa вы абсолютно правы, это тоже работает и, вероятно, лучший ответ, спасибо.

Ответ №1:

Итак, в конце концов, это рабочее решение, еще раз спасибо @fmw42 за указание правильного направления и @imochoa за предоставление кратчайшего рабочего кода.

 #!/usr/bin/env python3
import imutils
import cv2
import numpy as np
from scipy.stats import norm

image = cv2.imread("./test_images/test_artificial2.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Noise reduction
blurred = cv2.GaussianBlur(gray, (3, 3), 0)

# thresholding
mean, std = norm.fit(blurred)
thresh_min_value = int(mean   3.6*std)
thresh = cv2.threshold(blurred, thresh_min_value, 255, cv2.THRESH_BINARY)[1]

# find contours in the thresholded image
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

for c in cnts:
    # Moments
    M = cv2.moments(c)
    # Area of contour
    area = M["m00"]

    # Centroid of contour
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])

    # Perimeter
    perimeter = cv2.arcLength(c, True)

    print(f'{area=}')
    print(f'{perimeter=}')

    epsilon = 0.02 * perimeter
    approx = cv2.approxPolyDP(c, epsilon, True)
    print(f'{approx=}')

    # Create blank image
    blank_image = np.zeros(gray.shape, np.uint8)

    # Draw contour in the mask
    cv2.drawContours(blank_image, [approx], -1, (255, 255, 255), -1)

    # Create a mask to select pixels inside the figure
    mask_contour = blank_image == 255

    # Calculate the intensity from the grayscale image
    # filtering out the pixels where in the blank_image their value is not 255
    intensity = np.mean(gray[mask_contour])
    print(f'{intensity=}')

    cv2.imshow("Image", blank_image)
    cv2.waitKey(0)