Алгоритм адаптивного порога Брэдли
В настоящее время я работаю над реализацией алгоритма порога, называемого 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 Image]()
![Output 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, требуется несколько секунд, но на образце изображения это занимает всего несколько долей секунды. Изображение результата
Надеюсь, это кому-нибудь поможет.