Проблемы с обнаружением границ объекта из-за цвета или прозрачности в измерениях OpenCV и Feret

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

-Измерения: эта неточность приводит к неправильным измерениям диаметра Ферета.

Я работаю над проектом, в котором мне нужно обнаруживать и измерять объекты на изображениях. Каждое изображение содержит изображение предмета, и я использую метод **вычитания ** в OpenCV (cv2), чтобы удалить фон, а затем использую библиотеку imea для расчета максимального и минимального диаметров Ферета. Однако у меня возникают проблемы с определением границ элементов, особенно из-за их цвета или прозрачности.

Вот код, который я использую (я запускаю его в блокноте Jupyter):

import cv2 as cv
import os
import numpy as np
import matplotlib.pyplot as plt
# Clean up
plt.close('all')
import os
import numpy as np
from PIL import Image
from tqdm import tqdm
import cv2  # Import OpenCV for image preprocessing
import feret  # Import the feret module

# Function to display images in Jupyter
def display_image(image, title='Image'):
    plt.figure(figsize=(10, 10))
    plt.imshow(image, cmap='gray')
    plt.title(title)
    plt.axis('off')
    plt.show()

# Path to the folder containing images and the background image
image_folder = '.'
background_image_path = 'Main.jpeg'

# Load the background image
background_image = cv.imread(background_image_path, cv.IMREAD_COLOR)
if background_image is None:
    print(f"Unable to load background image: {background_image_path}")
    exit(1)

# Convert background image to grayscale
background_gray = cv.cvtColor(background_image, cv.COLOR_BGR2GRAY)

# Iterate through each file in the folder
for filename in os.listdir(image_folder):
    if filename.endswith('.jpeg') and filename != 'Main.jpeg':
        image_path = os.path.join(image_folder, filename)
        
        # Load the current image
        image = cv.imread(image_path, cv.IMREAD_COLOR)
        if image is None:
            print(f"Unable to load image: {image_path}")
            continue

        # Convert current image to grayscale
        image_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        
        # Perform background subtraction
        fgMask = cv.absdiff(background_gray, image_gray)
        
        # Apply a threshold to get the binary image
        _, fgMask = cv.threshold(fgMask, 50, 255, cv.THRESH_BINARY)
    
        # Display the original image and the foreground mask
        display_image(cv.cvtColor(image, cv.COLOR_BGR2RGB), title='Original Image')
        display_image(fgMask, title='Foreground Mask')

        # Plot Feret diameters on the preprocessed image
        feret.plot(fgMask)        
        # Calculate Feret diameters and angles
        maxf_length, minf_length, minf_angle, maxf_angle = feret.all(fgMask)
        
        # Print the results for the current image
        print(f"Filename: {filename}, Max Feret Length: {maxf_length}, Min Feret Length: {minf_length}")
        
    
        # result_path = os.path.join('path_to_save_results', f'fgMask_{filename}')
        # cv.imwrite(result_path, fgMask)

        # Pause to control the display in the notebook
        # This can be adjusted or removed as needed
        input("Press Enter to continue...")

# Clean up
plt.close('all')

# Optionally, print all results at the end
# for res in results:
#     print(f"Filename: {res['filename']}, Max Feret Length: {res['maxf_length']}, Min Feret Length: {res['minf_length']}")

Основной(Фоновый)

образец

образец

образец

образец

образец

больше образцов

Токовый выход

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

@CrisLuengo Прошу прощения за путаницу. Теперь я понимаю, что ранее разместил неправильный сценарий; это была устаревшая версия, в которой к маске применялись оттенки серого, и она не работала должным образом. Теперь я обновил свой пост правильным сценарием. Приносим извинения за возможные неудобства.

Agura 25.06.2024 17:15

@CrisLuengo Изоляция среднего объекта звучит как правильный путь, но я не мог понять, как это сделать. Также было бы здорово, если бы вы могли поделиться своей версией кода после его настройки, поскольку это поможет мне лучше понять вашу точку зрения. кажется, что вы получаете лучшие результаты!

Agura 25.06.2024 17:17
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Первое изменение, которое я бы сделал, — это взять максимальную разницу для каждого пикселя по каналам вместо преобразования в оттенки серого. При преобразовании в оттенки серого разные цвета одинаковой интенсивности будут выглядеть одинаково.

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

Это один из способов сделать это. Здесь я использую DIPlib, так как знаю его лучше, чем OpenCV, поэтому мне быстрее экспериментировать (к тому же я автор).

import diplib as dip

bg = dip.ImageRead('7AqYQsRe.jpg')  # the background image
img = dip.ImageRead('3Kvw57Jl.jpg')

diff = dip.MaximumTensorElement(dip.Abs(img - bg))  # your fgMask_gray
diff = dip.MedianFilter(diff)  # a minimal amount of filtering
objects = diff > 10  # a random value that seems to do quite well

# Pick the object in the center of the field of view, the quick and dirty way:
objects = dip.Label(objects)
label = objects[objects.Size(0) // 2, objects.Size(1) // 2]
mask = objects == label

# Measure
msr = dip.MeasurementTool.Measure(+mask, features=['Feret'])
print(msr)
max_len, min_len, perm_min_len, max_angle, min_angle = msr[1]['Feret']

Обратите внимание, что dip.MedianFilter по умолчанию использует круглую маску диаметром 7 пикселей. Я не пробовал регулировать этот параметр. Вы также можете попробовать более эффективные фильтры шума. Любая фильтрация может повлиять на расположение краев; более сильная фильтрация, скорее всего, уменьшит ваш объект. Но некоторые фильтры лучше сохраняют расположение края, чем другие. Линейные фильтры будут иметь наибольшее влияние. Имейте это в виду!

Пороговое изображение на самом деле выглядит очень хорошо. Мы можем использовать dip.AreaOpening(), чтобы удалить меньшие точки, оставив только настоящий объект. Но я включил логику в приведенный выше код, потому что она имеет смысл и, вероятно, более надежна. Лучший способ сохранить только объект в центре — измерить центроид каждого объекта и выбрать тот, центр тяжести которого находится ближе всего к центру изображения:

import numpy as np

msr = dip.MeasurementTool.Measure(objects, features=['Center', 'Feret'])
best_dist = 1e7
best_obj = 0
center = np.asarray(objects.Sizes()) // 2
for obj in msr.Objects():
    dist = np.linalg.norm(np.asarray(msr[obj]['Center']) - center)
    if dist < best_dist:
        best_dist = dist
        best_obj = obj

max_len, min_len, perm_min_len, max_angle, min_angle = msr[best_obj]['Feret']

Редактировать 1. Если объект не всегда находится в центре изображения, вы можете, например, найти самый большой объект (используя DIPlib, это функция «Размер») или придумать какую-то другую функцию, которая имеет значение для вашего приложения. .


Редактировать 2: Чтобы измерить только самый большой объект:

import diplib as dip

bg = dip.ImageRead('7AqYQsRe.jpg')  # the background image
img = dip.ImageRead('3Kvw57Jl.jpg')

diff = dip.MaximumTensorElement(dip.Abs(img - bg))  # your fgMask_gray
diff = dip.MedianFilter(diff)  # a minimal amount of filtering

objects = diff > 10  # a random value that seems to do quite well
objects = dip.Label(objects, mode='largest')  # the labeled image will have only one object in it
msr = dip.MeasurementTool.Measure(objects, features=['Feret'])
max_len, min_len, perm_min_len, max_angle, min_angle = msr[1]['Feret']

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

Agura 25.06.2024 18:47

@Agura Я добавил комментарий к ответу.

Cris Luengo 25.06.2024 18:50

Есть ли способ узнать, правильный ли расчет Ферета или нет, используя диплиб?

Agura 26.06.2024 18:33

@Агура. Я это реализовал, это всегда правильно. Посмотрите на маску, которую вы ввели в функцию. Если маска правильная, вы получите правильные измерения для этой маски. Если вы хотите увидеть, что он делает под обложкой, вам придется прочитать исходный код. Или вы можете прочитать эту запись в блоге, которую я написал, когда впервые реализовал алгоритм, который сейчас находится в DIPlib.

Cris Luengo 26.06.2024 18:59

поэтому он всегда будет обнаруживать элементы, даже если они не по центру?

Agura 26.06.2024 21:12

@Agura Обнаружение происходит при пороговом/бинаризации. В бинарном изображении мы применяем dip.Label() для анализа связанных компонентов. Если хотите, вы можете посмотреть на результат этого. С каждым подключенным компонентом теперь будет связано уникальное целое число (метка или идентификатор объекта). Функция измерения будет измерять каждый из объектов (поэтому все пиксели со значением 1 будут участвовать в измерениях объекта номер 1 и т. д.). Ни один из этих процессов не предполагает каких-либо догадок или неизвестных факторов. Единственная сложная часть — это определение хорошего порога и фильтрация до и после порога.

Cris Luengo 26.06.2024 21:33

Я понимаю. Большое спасибо за помощь. К сожалению, я все еще сталкиваюсь с серьезными проблемами из-за того, что фон не одинаков для всех частей. Мне нужно просмотреть более 1000 объектов, и моя команда допустила существенную ошибку, разрешив свету отражаться на фоне вместо использования однородного цвета. Вы даже не представляете, насколько важен для меня этот этап, чтобы перейти к более серьёзному. Без чистого старта я не смогу добиться хороших результатов. Мне бы хотелось встретиться с вами лично или что-то в этом роде, чтобы показать вам все данные.

Agura 27.06.2024 16:05

Я думаю, здесь вам нужно решить, будет ли стоимость разработки надежного алгоритма, который обрабатывает все эти случаи, выше или ниже, чем стоимость повторного отображения всех этих частей в лучших условиях. Или вы можете вручную обработать каждое из бинаризированных изображений, исправив контур там, где он идет не так.

Cris Luengo 27.06.2024 16:29

Для того, чтобы представить все детали, потребовалась полная установка, включая 3 HD-камеры и много ручной работы, поэтому они оставили мне это, лол, очень честно.

Agura 27.06.2024 16:31

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

Agura 28.06.2024 20:20

@Agura В вызове dip.MeasurementTool.Measure() добавьте 'Size' в список объектов, затем выберите объект самого большого размера. На самом деле, вы можете упростить это, добавив mode='largest' к вызову dip.Label.

Cris Luengo 28.06.2024 21:00

Понятно, спасибо, сработало! У меня также есть еще один вопрос. Я сохранил все изображения после применения вычитания и маски. Результаты выглядят великолепно, и теперь я хочу извлечь из них результаты измерений. Однако код продолжает возвращать ошибки, а сайт документации не очень понятен. Не могли бы вы помочь мне разобраться в проблеме? Вот код, который я использую: img = dip.ImageRead(img_path) diff = dip.MaximumTensorElement(img) msr = dip.MeasurementTool.Measure(diff, features=['Feret']) max_len, min_len = msr[1]['Feret'][0], msr[1]['Feret'][1] # Max and Min Feret diameters

Agura 02.07.2024 11:39

он продолжает возвращать «Maxinf» в качестве значения

Agura 02.07.2024 11:40

@Agura Входные данные для функции измерения должны быть помеченным изображением. Вам нужно бинаризировать ваш diff, а затем применить функцию dip.Label.

Cris Luengo 02.07.2024 15:44

не могли бы вы просветить меня, как?

Agura 02.07.2024 17:44

@Agura Я добавил код внизу ответа.

Cris Luengo 02.07.2024 17:50

Другие вопросы по теме