-Обнаружение объектов: из-за цвета или прозрачности элементов границы не определяются точно с помощью метода вычитания фона.
-Измерения: эта неточность приводит к неправильным измерениям диаметра Ферета.
Я работаю над проектом, в котором мне нужно обнаруживать и измерять объекты на изображениях. Каждое изображение содержит изображение предмета, и я использую метод **вычитания ** в 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 Изоляция среднего объекта звучит как правильный путь, но я не мог понять, как это сделать. Также было бы здорово, если бы вы могли поделиться своей версией кода после его настройки, поскольку это поможет мне лучше понять вашу точку зрения. кажется, что вы получаете лучшие результаты!
Первое изменение, которое я бы сделал, — это взять максимальную разницу для каждого пикселя по каналам вместо преобразования в оттенки серого. При преобразовании в оттенки серого разные цвета одинаковой интенсивности будут выглядеть одинаково.
Далее опустите порог. Вы выбрали 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 Я добавил комментарий к ответу.
Есть ли способ узнать, правильный ли расчет Ферета или нет, используя диплиб?
@Агура. Я это реализовал, это всегда правильно. Посмотрите на маску, которую вы ввели в функцию. Если маска правильная, вы получите правильные измерения для этой маски. Если вы хотите увидеть, что он делает под обложкой, вам придется прочитать исходный код. Или вы можете прочитать эту запись в блоге, которую я написал, когда впервые реализовал алгоритм, который сейчас находится в DIPlib.
поэтому он всегда будет обнаруживать элементы, даже если они не по центру?
@Agura Обнаружение происходит при пороговом/бинаризации. В бинарном изображении мы применяем dip.Label()
для анализа связанных компонентов. Если хотите, вы можете посмотреть на результат этого. С каждым подключенным компонентом теперь будет связано уникальное целое число (метка или идентификатор объекта). Функция измерения будет измерять каждый из объектов (поэтому все пиксели со значением 1 будут участвовать в измерениях объекта номер 1 и т. д.). Ни один из этих процессов не предполагает каких-либо догадок или неизвестных факторов. Единственная сложная часть — это определение хорошего порога и фильтрация до и после порога.
Я понимаю. Большое спасибо за помощь. К сожалению, я все еще сталкиваюсь с серьезными проблемами из-за того, что фон не одинаков для всех частей. Мне нужно просмотреть более 1000 объектов, и моя команда допустила существенную ошибку, разрешив свету отражаться на фоне вместо использования однородного цвета. Вы даже не представляете, насколько важен для меня этот этап, чтобы перейти к более серьёзному. Без чистого старта я не смогу добиться хороших результатов. Мне бы хотелось встретиться с вами лично или что-то в этом роде, чтобы показать вам все данные.
Я думаю, здесь вам нужно решить, будет ли стоимость разработки надежного алгоритма, который обрабатывает все эти случаи, выше или ниже, чем стоимость повторного отображения всех этих частей в лучших условиях. Или вы можете вручную обработать каждое из бинаризированных изображений, исправив контур там, где он идет не так.
Для того, чтобы представить все детали, потребовалась полная установка, включая 3 HD-камеры и много ручной работы, поэтому они оставили мне это, лол, очень честно.
Еще раз здравствуйте, Крис, я немного подправил ваш код и не смог определить функцию, которая обнаруживает самый большой объект, а не тот, что посередине. Есть ли какой-нибудь возможный способ сделать это?
@Agura В вызове dip.MeasurementTool.Measure()
добавьте 'Size'
в список объектов, затем выберите объект самого большого размера. На самом деле, вы можете упростить это, добавив mode='largest'
к вызову dip.Label
.
Понятно, спасибо, сработало! У меня также есть еще один вопрос. Я сохранил все изображения после применения вычитания и маски. Результаты выглядят великолепно, и теперь я хочу извлечь из них результаты измерений. Однако код продолжает возвращать ошибки, а сайт документации не очень понятен. Не могли бы вы помочь мне разобраться в проблеме? Вот код, который я использую: 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
он продолжает возвращать «Maxinf» в качестве значения
@Agura Входные данные для функции измерения должны быть помеченным изображением. Вам нужно бинаризировать ваш diff
, а затем применить функцию dip.Label
.
не могли бы вы просветить меня, как?
@Agura Я добавил код внизу ответа.
@CrisLuengo Прошу прощения за путаницу. Теперь я понимаю, что ранее разместил неправильный сценарий; это была устаревшая версия, в которой к маске применялись оттенки серого, и она не работала должным образом. Теперь я обновил свой пост правильным сценарием. Приносим извинения за возможные неудобства.