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

Итак, одной из проблем, с которыми мы столкнулись в прошлом блоге, был размер объектов, идентифицированных на изображении, и надлежащий анализ этих объектов.

Продолжение кода из прошлого блога.

for label_ind, label_coords in enumerate(ndimage.find_objects(labels)):
    cell = im_gray[label_coords]
   
    if np.product(cell.shape) < 10: 
        print('Label {} is too small! Setting to 0.'.format(label_ind))
        mask = np.where(labels==label_ind+1, 0, mask)


labels, nlabels = ndimage.label(mask)
print('There are now {} separate components / objects detected.'.format(nlabels))

Таким образом, в приведенных выше строках кода мы пытаемся определить размеры объектов на изображении, и если они ниже заданного порога, мы пытаемся отправить сообщение для печати. Давайте проверим вывод.

Давайте попробуем и посмотрим, как код пытается определить размер идентифицируемых объектов.

fig, axes = plt.subplots(1,6, figsize=(10,6))

for ii, obj_indices in enumerate(ndimage.find_objects(labels)[0:6]):
    cell = im_gray[obj_indices]
    axes[ii].imshow(cell, cmap='gray')
    axes[ii].axis('off')
    axes[ii].set_title('Label #{}\nSize: {}'.format(ii+1, cell.shape))

plt.tight_layout()
plt.show()

Ярлык № 2 имеет проблему «смежной ячейки»: две ячейки считаются частью одного и того же объекта. Здесь мы можем посмотреть, можем ли мы уменьшить маску, чтобы «раскрыть» различия между ячейками. Это называется эрозией маски. Затем мы можем повторно расширить его, чтобы восстановить исходные пропорции.

Теперь мы создадим маску, чтобы лучше идентифицировать объекты.

two_cell_indices = ndimage.find_objects(labels)[1]
cell_mask = mask[two_cell_indices]
cell_mask_opened = ndimage.binary_opening(cell_mask, iterations=8)
fig, axes = plt.subplots(1,4, figsize=(12,4))

axes[0].imshow(im_gray[two_cell_indices], cmap='gray')
axes[0].set_title('Original object')
axes[1].imshow(mask[two_cell_indices], cmap='gray')
axes[1].set_title('Original mask')
axes[2].imshow(cell_mask_opened, cmap='gray')
axes[2].set_title('Opened mask')
axes[3].imshow(im_gray[two_cell_indices]*cell_mask_opened, cmap='gray')
axes[3].set_title('Opened object')
for ax in axes:
    ax.axis('off')
plt.tight_layout()
plt.show()

Наконец, нам нужно закодировать каждый label_mask в строку «run line encoded». По сути, мы проходим через массив, и когда мы находим пиксель, который является частью маски, мы индексируем его и подсчитываем, сколько последующих пикселей также являются частью маски. Мы повторяем это каждый раз, когда видим новую точку начала пикселя.

Я нашел хорошую функцию для выполнения RLE из ядра пользователя Kaggle Рахлина, которую я скопировал сюда.

def rle_encoding(x):
    '''
    x: numpy array of shape (height, width), 1 - mask, 0 - background
    Returns run length as list
    '''
    dots = np.where(x.T.flatten()==1)[0] # .T sets Fortran order down-then-right
    run_lengths = []
    prev = -2
    for b in dots:
        if (b>prev+1): run_lengths.extend((b+1, 0))
        run_lengths[-1] += 1
        prev = b
    return " ".join([str(i) for i in run_lengths])

print('RLE Encoding for the current mask is: {}'.format(rle_encoding(label_mask)))

Кодирование длин серий - это форма сжатия данных без потерь, при которой серии данных сохраняются как единое значение данных и счетчик, а не как исходная серия. Это наиболее полезно для данных, содержащих много таких прогонов.

Это охватывает большинство основ обработки изображений. Спасибо за чтение. Продолжай учиться.

Ваше здоровье.