Алгоритм адаптивного порога Брэдли

В настоящее время я работаю над реализацией алгоритма порога, называемого Bradley Adaptive Thresholding.

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

Вот две ссылки, по которым я следовал, чтобы создать алгоритм Bradley Adaptive Thresholding.

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf

Пример Брэдли с адаптивным порогом Github

Вот раздел моего исходного кода на Python где я запускаю алгоритм и сохраняю изображение. Я использую Python Imaging Library и никаких других инструментов, чтобы выполнить то, что я хочу сделать.

def get_bradley_binary(inp_im):
    w, h = inp_im.size
    s, t = (w / 8, 0.15)

    int_im = Image.new('L', (w, h))
    out_im = Image.new('L', (w, h))

    for i in range(w):
        summ = 0
        for j in range(h):
            index = j * w + i

            summ += get_pixel_offs(inp_im, index)

            if i == 0:
                set_pixel_offs(int_im, index, summ)
            else:
                temp = get_pixel_offs(int_im, index - 1) + summ
                set_pixel_offs(int_im, index, temp)

    for i in range(w):
        for j in range(h):
            index = j * w + i

            x1,x2,y1,y2 = (i-s/2, i+s/2, j-s/2, j+s/2)

            x1 = 0 if x1 < 0 else x1
            x2 = w - 1 if x2 >= w else x2
            y1 = 0 if y1 < 0 else y1
            y2 = h - 1 if y2 >= h else y2

            count = (x2 - x1) * (y2 - y1)

            a1 = get_pixel_offs(int_im, y2 * w + x2)
            a2 = get_pixel_offs(int_im, y1 * w + x2)
            a3 = get_pixel_offs(int_im, y2 * w + x1)
            a4 = get_pixel_offs(int_im, y1 * w + x1)

            summ = a1 - a2 - a3 + a4

            temp = get_pixel_offs(inp_im, index)
            if temp * count < summ * (1.0 - t):
                set_pixel_offs(out_im, index, 0)
            else:
                set_pixel_offs(out_im, index, 255)

    return out_im

Вот раздел моего кода, который иллюстрирует реализацию этих методов set и get, которые вы раньше не видели.

def get_offs(image, x, y):
    return y * image.size[0] + x

def get_xy(image, offs):
    return (offs % image.size[0], int(offs / image.size[0]))

def set_pixel_xy(image, x, y, data):
    image.load()[x, y] = data

def set_pixel_offs(image, offs, data):
    x, y = get_xy(image, offs)
    image.load()[x, y] = data

def get_pixel_offs(image, offs):
    return image.getdata()[offs]

def get_pixel_xy(image, x, y):
    return image.getdata()[get_offs(image, x, y)]

И, наконец, вот входные и выходные изображения. Это те же изображения, которые использовались в оригинальной исследовательской работе в первой ссылке, которую я вам предоставил. Примечание. Выходное изображение почти полностью белое, и его может быть трудно увидеть, но я все равно предоставил его на случай, если кто-то действительно захочет получить его для справки.

Input ImageOutput Image

Ответ 1

Вы не можете создать интегральное изображение с PIL так, как вы это делаете, потому что изображение, в которое вы упаковываете данные, не может принимать значения свыше 255. Значения в интегральном изображении становятся очень большими, потому что они являются суммами пикселей выше и слева (см. стр. 3 вашей белой бумаги ниже).

enter image description here

Они вырастут намного больше, чем 255, поэтому для их хранения нужно 32 бита на пиксель.

Вы можете проверить это, создав изображение PIL в режиме "L", а затем установив пиксель на 1000000 или на большее число. Затем, когда вы прочитаете обратно значение, оно вернет 255.

>>> from PIL import Image
>>> img = Image.new('L', (100,100))
>>> img.putpixel((0,0), 100000)
>>> print(list(img.getdata())[0])
255

РЕДАКТИРОВАТЬ: После прочтения документации PIL, вы можете использовать PIL, если вы создаете свое целостное изображение в режиме "I" вместо режима "L". Это должно обеспечить 32 бита на пиксель.

По этой причине я рекомендую Numpy вместо PIL.

Ниже приведена перезапись вашей пороговой функции с использованием Numpy вместо PIL, и я получаю правильный/ожидаемый результат. Обратите внимание, что я создаю свое целостное изображение, используя массив uint32. Я использовал тот же самый пример C на Github, который вы использовали для своего перевода:

import numpy as np

def adaptive_thresh(input_img):

    h, w = input_img.shape

    S = w/8
    s2 = S/2
    T = 15.0

    #integral img
    int_img = np.zeros_like(input_img, dtype=np.uint32)
    for col in range(w):
        for row in range(h):
            int_img[row,col] = input_img[0:row,0:col].sum()

    #output img
    out_img = np.zeros_like(input_img)    

    for col in range(w):
        for row in range(h):
            #SxS region
            y0 = max(row-s2, 0)
            y1 = min(row+s2, h-1)
            x0 = max(col-s2, 0)
            x1 = min(col+s2, w-1)

            count = (y1-y0)*(x1-x0)

            sum_ = int_img[y1, x1]-int_img[y0, x1]-int_img[y1, x0]+int_img[y0, x0]

            if input_img[row, col]*count < sum_*(100.-T)/100.:
                out_img[row,col] = 0
            else:
                out_img[row,col] = 255

    return out_img

output

Ответ 2

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

import numpy, gc
from ctypes import *    

def adaptive_threshold(self):
    gc.collect()
    gc.disable()

    w, h = self._image.width, self._image.height
    s, t = w//8, 0.15
    summ = c_uint32(0)
    count = c_uint32(0)
    pixels = self._pixels

    int_img = numpy.ndarray(shape=(w, h), dtype=c_int64)

    for i in range(w):
        summ.value = 0
        for j in range(h):
            summ.value += sum(pixels[i, j])
            if i != 0:
                int_img[i, j] = int_img[i - 1, j] + summ.value
            else:
                int_img[i, j] = summ.value


    x1, x2, y1, y2 = c_uint16(0), c_uint16(0), c_uint16(0), c_uint16(0)
    for i in range(w):
        for j in range(h):
            x1.value = max(i - s // 2, 0)
            x2.value = min(i + s // 2, w - 1)
            y1.value = max(j - s // 2, 0)
            y2.value = min(j + s // 2, h - 1)

            count.value = (x2.value - x1.value) * (y2.value - y1.value)

            summ.value = int_img[x2.value][y2.value] - int_img[x1.value][y2.value] - \
                int_img[x2.value][y1.value] + int_img[x1.value][y1.value]

            if sum(pixels[i, j]) * count.value < summ.value * (1.0 - t):
                pixels[i, j] = 0, 0, 0
            else:
                pixels[i, j] = 255, 255, 255

    gc.enable()

Обратите внимание, что это часть класса. Он в основном имеет две переменные: _image, которая указывает на фактическое изображение, и _pixels, который является классом PixelAccess, который обеспечивает доступ к пикселям в качестве заданных значений. Я использовал деление по полу (//) вместо обычного деления (/), потому что оно гарантирует, что все значения являются целыми. Пока результаты выглядят хорошо. Я использовал типы данных C, чтобы контролировать использование памяти и сохранять значения в фиксированных позициях. Насколько я понимаю, это помогает контролировать небольшое количество данных для минимизации фрагментированных данных.

Плюс это последний квартал 2018 года. Люди до сих пор используют PIL, и, честно говоря, сейчас это делает. Это прекрасно работает для цветового пространства RGB. Если вы используете это на общих изображениях, вы можете преобразовать данные изображения в пространство RGB, используя:

Image.convert('RGB')

где "Изображение" является экземпляром открытого изображения

Для изображений, которые считаются HD-изображениями размером 1200x700, требуется несколько секунд, но на образце изображения это занимает всего несколько долей секунды. Изображение результата

Надеюсь, это кому-нибудь поможет.