Я хотел бы использовать приложение FiftyOne для оценки прогнозов сегментации экземпляров. Для начала я использовал загружаемый набор данных для быстрого старта и создал свой собственный файл samples.json
для наземных данных и прогнозных данных, который можно загрузить с помощью Dataset.from_dir
. Файл json в руководстве содержит только ограничивающие рамки, но не маски. В документации я нашел только маски, которые установлены на None
, но они мне понадобятся.
{
"samples": [
{
"filepath": "some_image.png",
"tags": [
"validation"
],
"metadata": null,
"uniqueness": 0.998,
"ground_truth": {
"_cls": "Detections",
"detections": [
{
"_id": {
"$oid": "efd81c917c9a49dc9f8aca3f"
},
"_cls": "Detection",
"label": "1",
"bounding_box": [
0.62609375,
0.27044921875,
0.030078124999999956,
0.009550781250000001
],
"mask": ???,
}
]
},
"predictions": {
"_cls": "Detections",
"detections": [
{
"_id": {
"$oid": "6db44c71c1414b8989c92255"
},
"_cls": "Detection",
"label": "1",
"bounding_box": [
0.3303889036178589,
0.4432219862937927,
0.07914596796035767,
0.02226179838180542
],
"mask": ???,
"confidence": 0.999962329864502
},
]
}
}
]
}
У меня проблема в том, как мне создать свойство mask
для маски сегментации? Это пустой массив, который по умолчанию не сериализуется. Dataset.from_dir
использует функцию core.utils.deserialize_numpy_array
для загрузки набора данных, поэтому я безуспешно пытался использовать serialize_numpy_array
для сохранения набора данных.
Итак, как лучше всего записать маску в файл json, который можно десериализовать? Спасибо!
Синтаксис Dataset.from_dir()
обычно используется для четко определенных типов наборов данных, таких как набор данных, хранящийся на диске в формате COCODetectionDataset
.
В вашем случае при загрузке пользовательского набора данных, который напрямую не соответствует одному из связанных типов набора данных, рекомендуется написать простой цикл Python и создание набора данных по одной выборке за раз.
В этом случае вам просто нужно загрузите свою маску в виде массива numpy и сохраните ее в атрибуте mask
вашего объекта обнаружения.
import glob
import fiftyone as fo
images_patt = "/path/to/images/*"
# Ex: your custom label format
annotations = {
"/path/to/images/000001.jpg": [
{"bbox": ..., "label": ..., "mask": ...},
...
],
...
}
# Create samples for your data
samples = []
for filepath in glob.glob(images_patt):
sample = fo.Sample(filepath=filepath)
# Convert detections to FiftyOne format
detections = []
for objects in annotations[filepath]:
label = obj["label"]
# Bounding box coordinates should be relative values
# in [0, 1] in the following format:
# [top-left-x, top-left-y, width, height]
bounding_box = obj["bbox"]
# Boolean or 0/1 Numpy array
mask = obj["mask"]
detection = fo.Detection(
label=label,
bounding_box=bounding_box,
mask=mask,
)
detections.append(detection)
# Store detections in a field name of your choice
sample["ground_truth"] = fo.Detections(detections=detections)
samples.append(sample)
# Create dataset
dataset = fo.Dataset("my-detection-dataset")
dataset.add_samples(samples)
Прогнозы могут быть загружены одновременно с реальными данными, или вы всегда можете перебирать свой набор данных в будущем и добавить прогнозы позже:
import fiftyone as fo
# Ex: your custom predictions format
predictions = {
"/path/to/images/000001.jpg": [
{"bbox": ..., "label": ..., "mask": ..., "score": ...},
...
],
...
}
# Add predictions to your samples
for sample in dataset:
filepath = sample.filepath
# Convert predictions to FiftyOne format
detections = []
for obj in predictions[filepath]:
label = obj["label"]
confidence = obj["score"]
# Bounding box coordinates should be relative values
# in [0, 1] in the following format:
# [top-left-x, top-left-y, width, height]
bounding_box = obj["bbox"]
# Boolean or 0/1 Numpy array
mask = obj["mask"]
detection = fo.Detection(
label=label,
bounding_box=bounding_box,
confidence=confidence,
)
detection["mask"] = mask
detections.append(detection)
# Store detections in a field name of your choice
sample["predictions"] = fo.Detections(detections=detections)
sample.save()
Обратите внимание, что точная структура анализа вашего пользовательского формата метки будет зависеть от того, как вы храните данные. Это всего лишь один пример, в котором метки хранятся в словаре с указанием путей к файлам мультимедиа. Сначала вам может потребоваться проанализировать и преобразовать ваши метки, например, в ожидаемый формат ограничивающей рамки.
Эти дополнительные поля должны быть созданы явно для вашего набора данных и не должны копироваться из одного набора данных в другой. Например, поле «eval_fp» заполняется путем запуска оценки обнаружения в вашем наборе данных: voxel51.com/docs/fiftyone/user_guide/evaluation.html#detections, а поле «уникальность» создается с помощью этого рабочего процесса: voxel51.com/docs/fiftyone/user_guide/….
О, круто. Немного не интуитивно понятно, что evaluate_detections
изменяет сам набор данных, но в любом случае это хорошая новость.
Основная причина использования оценки в FiftyOne заключается в том, что она добавляет поля в сам набор данных. В других инструментах оценки вы просто получаете статистику по всему набору данных, но FiftyOne будет хранить отдельные ложные/истинные положительные/отрицательные результаты непосредственно на ваших ярлыках, чтобы вы могли запросить их и изучить, чтобы увидеть, как ваша модель работает на конкретных примерах.
Раньше я использовал одну или две оценки, но только коснулся поверхности и попробовал некоторые базовые вещи. Есть что исследовать.
Еще один вопрос, если не возражаете. Я создал образцы и добавил их в набор данных, который теперь содержит маски Ground_truth и предсказаний (логические массивы numpy), но маски не отображаются в приложении, когда я его запускаю, только ограничивающие прямоугольники. Означает ли это, что я сделал что-то не так при создании набора данных, или нужно как-то настроить отображение? (Когда я загружаю набор данных кокоса с диска, предоставляя необходимые типы меток, все в порядке.)
Правильно, похоже, что маски были загружены неправильно. Приложение автоматически отобразит маски, если они имеют правильный формат. Не могли бы вы перейти на Slack-канал FiftyOne Users и предоставить больше контекста, будет легче отлаживать: join.slack.com/t/fiftyone-users/shared_invite/…
Спасибо за предложение! Таким образом, я действительно могу создать набор данных, который можно передать в приложение 51, такое как
session = fo.launch_app(dataset)
. Но, например, я не могу сделать следующееdataset.sort_by("eval_fp", reverse=True).filter_labels("predictions", F("eval") == "fp")
, потому что не существует поля «eval_fp». Создание «samples.json» (аналогично набору данных быстрого запуска) и загрузка его с помощьюDataset.from_dir
каким-то образом создает набор данных, который дополнен некоторыми дополнительными полезными полями.