first commit

This commit is contained in:
Eddy
2014-10-10 17:36:02 +04:00
commit 1a63497f04
62 changed files with 9055 additions and 0 deletions

19
erosion-dilation/Makefile Normal file
View File

@@ -0,0 +1,19 @@
PROGRAM = binmorph_test
CXX.SRCS = binmorph.c main.c
CC = gcc
LDFLAGS = -lm -fopenmp
CXX = gcc
DEFINES = -DEBUG -DOMP
CFLAGS = -fopenmp -Wall -Werror -O3 -std=gnu99 $(DEFINES)
OBJS = $(CXX.SRCS:.c=.o)
all : $(PROGRAM) clean
binmorph.c : cclabling.h cclabling_1.h dilation.h fs_filter.h fc_filter.h
$(PROGRAM) : $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM)
clean:
/bin/rm -f *.o *~
depend:
$(CXX) -MM $(CXX.SRCS)
### <DEPENDENCIES ON .h FILES GO HERE>
# name1.o : header1.h header2.h ...

523
erosion-dilation/README Normal file
View File

@@ -0,0 +1,523 @@
************************************
СООБРАЖЕНИЯ ПО ПОВОДУ ЭРОЗИИ/ДИЛЯЦИИ
************************************
Эрозия
00000 00000
01110 00000
01110 => 00100
01110 00000
00000 00000
Бит равен единице лишь тогда, когда присутствуют все его 4 связи.
Т.е. для срединных битов байта выполняем & текущего байта с байтами верхней и нижней строк. Для крайних битов необходимо проверить еще и 4-связные байты:
ABC
DEF
GHK
Младший бит E проверяется по старшему биту F, младшим битам B и H и второму биту E.
Старший бит E проверяется по младшему биту D, старшим битам B и E и седьмому биту E.
Таким образом, алгоритм получается следующим:
1. подменяем E маской[E] (эрозия внутренних битов)
2. делаем &: E = E & B & H
3. корректируем старший бит E по младшему D и младший бит E по старшему F.
ПРОВЕРИТЬ, ЧТО БЫСТРЕЙ: (A&0X80)>>8 ИЛИ РАЗЫМЕНОВАНИЕ УКАЗАТЕЛЯ (ПО ТАБЛИЦЕ)
Диляция
00000 00110
00110 01111
01010 => 11111
01100 11110
00000 01100
Обратная ситуация: вместо & делаем | :
1. подменяем E маской (диляция внутренних битов)
2. делаем |: E = E | B | H
3. корректируем старший и младший биты операцией |.
**************************************************
СООБРАЖЕНИЯ ПО ПОВОДУ ВЫДЕЛЕНИЯ СВЯЗАННЫХ ОБЛАСТЕЙ
**************************************************
Вернемся к тому же общему случаю: искомый пиксель нахдится в байте E.
ABC
DEF
GHK
Фильтрация 4-связанных областей схожа с операцией диляции за исключением собственно
логики - в отсечении пикселей, не являющихся 4-связанными, необходимо провести операцию:
E4 = E & ( B|H|(E<<1)|(E>>1)|(D<<7)|(F>>7) )
Т.е. мы оставляем пиксель в E лишь в том случае, если хотя бы один из его
4-связанных соседей присутствует.
Соответственно, для крайних пикселей (крайние столбцы/строки) выражение для E4
будет видоизменяться (для крайних строк - наличием/отсутствием B,H; для крайних
столбцов - наличием/отсутствием выражений с D, F).
Для упрощения записи можно было бы сделать макрос при помощи boost, но что-то
лень мне в дебри буста погружаться, поэтому проблему решаю простым копипастом.
Кстати, здесь нет проверки A,C,G и K, иначе пришлось бы еще больше кода наворачивать
(добавились бы особые случаи для углов изображения).
Общее выражение для поиска 8-связанных областей несколько видоизменится:
пусть {x} = (x|(x<<1)|(x>>1)), тогда
E8 = E & ( {B|H} | ((E<<1)|(E>>1)) | ((C|F|K)>>7) | ((A|D|G)<<7) )
Вычислительная сложность сильно возрастает, причем многие операции вычисляются по
два раза. Пожалуй, можно попытаться немного ускорить вычисления, если заранее
подготовить матрицу {x} (и матрицы (x<<7) и (x>>7) ???).
/* (сомнительно) Аналогично, заранее подготовив матрицы x<<7) и (x>>7) можно
* попытаться ускорить поиск E4 */
Когда мы уже имеем отфильтрованное изображение, нужно "всего лишь" пробежаться
по нему в поисках областей, со всех сторон ограниченных нулями. Грубо говоря,
мы маркируем 8-связанные области без проверки на 8-связанность.
Первое, что приходит на ум - двухпроходная маркировка: сначала мы пробегаемся
по связанной области, постепенно расширяя границы заключающего ее прямоугольника.
Затем мы выделяем память для этого кусочка и заполняем его, пробегаясь по нашей
области еще раз (ну или делаем полную копию содержимого бокса, а затем выкидываем
все, что не принадлежит искомой связанной области).
Еще вариант - сразу выделять область памяти (вспомогательную) размером со ВСЕ
первоначальное изображение (гениально!). В этом случае мы сможем сразу же копировать
в эту область памяти нашу х-связанную область, а затем, когда она целиком будет
скопирована и будут ясны параметры описывающего ее прямоугольника, выделяем уже
память именно под эту область и копируем в нее содержимое бокса.
Логично было бы преобразовать картинку до анализа в бинарный вид, поэтому сначала
будем предполагать, что мы имеем дело с бинарным изображением (а не "сжатым"
битовым).
Теперь каждый пиксель нашей матрицы соответствует одному пикселю изображения
ABC
DEF
GHK
На ум приходит использовать метод шагающих квадратов, однако, он применяется
для обхода границ -> мало чем поможет (разве что первоначальным определением
размеров бокса, но я уже решил делать лишь один проход обнаружения связанных
областей). Интернета под рукой нет, чтобы подсмотреть наиболее эффективный
способ, поэтому буду изобретать велосипед.
=== Нулевое приближение ===
/*
Работаем с копией изображения, т.к. уже пройденные значения будем обнулять.
*/
Итак, выделим память под промежуточный буфер (сразу обнулив ее) и начнем
проходить изображение с левого верхнего угла. Как только мы натыкаемся
на единицу в E, записываем в соответствующие координаты буфера 1.
Далее смотрим, куда нам нужно двигаться. Считаем, что в A,B,C и D нули (т.к. там -
уже пройденная область). Нам необходимо проверить пиксели F, G, H и K.
Теперь каждый из них становится E. Если кто-то из них установлен в 1, проверяем
его соседей и т.д. Однако, на всех шагах, отличных от первого, мы уже обязаны
проверить ВСЕХ СОСЕДЕЙ, т.к. не факт, что наша область не представляет собою
какой-нибудь извращенной фигуры с отростками и многочисленными полостями.
На ум приходит рекурсия, однако, учитывая то, что изображения могут быть довольно-таки
огромными, с горем расстаюсь с этой идеей. А так было бы хорошо: рекурсия сделала
бы очень простым этот алгоритм. Но регистры не бесконечные.
Кстати, если не преобразовывать изображение в бинарное, то вполне возможны случаи,
когда один и тот же байт будет проходиться вплоть до четырех раз!
После копирования каждого пикселя с 1 в результирующий буфер, "стираем" его
с оригинала (сбросом в 0). По окончанию операций обнуляем буфер.
Тут мне опять пришла в голову идея о шагающих квадратах: а что, если в первый
проход пробежаться по границе области, определить описывающий ее прямоугольник,
выделить буфер по размеру прямоугольника (это - как раз искомое возвращаемое
функцией значение); после этого, проходя по объекту, пиксель за пикселем копировать
его в буфер, "стирая" с оригинала. Правда, здесь опять та же самая проблема:
нет гарантии, что за один проход все будет сделано.
=== "Гюйгенс". ===
Один из вариантов прохождения по изображению в поисках связанных областей (который,
собственно, первым пришел мне в голову еще вчера, но лучше всего, конечно, он бы
выглядел в рекурсивном исполнении).
== лирика по поводу рекурсии ==
Еще раз, чтобы убедиться лишний раз в том, что рекурсия невозможна, я набросал
элементарнейшую проверялку:
#include <stdio.h>
int incr(int a){
fprintf(stderr, "%d ", a++);
incr(a);
return a;
}
main(){
incr(1);
return 0;
}
Проверялочка эта должна вылетать с сегфолтом как только скончаются все ресурсы
на рекурсию. Получилось вот что:
... 261504 Ошибка сегментирования (core dumped)
... 261350 Ошибка сегментирования (core dumped)
... 261333 Ошибка сегментирования (core dumped)
ЕМНИП, что-то подобное было на ЛОРе. И, возможно, предел этот даже как-то регулируется.
Число это подозрительно близко к 256к (262144): возможно, оно так и есть (просто
еще кучу места в стеке занимают всякие вспомогательные буферы из stdio).
Я немного видоизменил программку: она теперь еще и по 1к выделяет:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int incr(int a){
char *x = malloc(1024);
memset(x, 1, 1024);
fprintf(stderr, "%d ", a++);
incr(a);
return a;
}
main(){
incr(1);
return 0;
}
Теперь цифры "более другие":
... 174258 Ошибка сегментирования (core dumped)
... 174372 Ошибка сегментирования (core dumped)
... 174325 Ошибка сегментирования (core dumped)
Кто сожрал 85к? Почему именно 85к? Заменяю выделение памяти на `char x = 2;` -
получаю то же самое. Добавление переменных ничего не меняет.
В любом случае, все-таки про рекурсию придется забыть: если искомая область будет
иметь площадь, превышающую эдак 170 тысяч пикселей (а это не так уж и много), то
я получу большой фигвам.
## конец лирики
Принцип Гюйгенса прост: каждая точка волнового фронта (в нашем случае - точка
внутри искомой области) является источником нового волнового фронта (в нашем
случае - центром, от которого "расходится" поисковая волна).
Пробегаясь такой "волной" от первого обнаруженного пикселя, принадлежащего
искомой фигуре, мы пройдем все ее закоулки.
Однако, проблема в том, как реализовать это без бешеного количества разыменования
указателей и проверок.
Долго кумекая, я так и не придумал, как реализовать "Гюйгенса", не вводя уйму
промежуточных матриц.
### "Гюйгенс модифицированный" ###
Для ограничения одной-единственной вспомогательной матрицей, можно попробовать
оставить одну "волну": от первоначальной точки проводим расходящуюся во все
стороны "волну". Пусть для простоты она будет "квадратной".
Пробегаясь по всем точкам очередного фронта "волны", кроме особых (о них -
позже, их тоже надо контролировать), "стираем" единицы с изображения, перенося их в маску.
Как только натыкаемся на нуль, если до этого тоже был нуль, заносим единичку в
матрицу спец.маски в соответствующий пиксель, а также в пиксель,
соседний с текущим, но расположеный на "волне" с радиусом на 1 больше (т.е. для левой
поверхности переносим пиксель влево на 1, для правой - вправо на 1 и т.п.).
Если натыкаемся на 1, делаем обнуление соответствующего пикселя на "волне" с радиусом
на 1 больше + его предыдущего соседа в спец.маске. Таким образом, "тени" получаются постепенно
сходящимися. Но все равно, остается возможность того, что эти "тени" что-нибудь да
сокроют, не говоря уже о том, что эта "волна" только в очень редких случаях сразу
выявит хотя бы внешний контур искомой фигуры.
Именно для этого и нужна наша спец.маска: как только "кончится" искомая фигура по
"большой волне", нужно будет просмотреть все пиксели маски (внутри уже получившегося
прямоугольника + 1 по радиусу, т.к. снаружи все равно нули), пробегая по их
внешней границе: как только обнаруживаем 1 в изображении, "выпускаем" новую "волну".
Пока бежим первый раз, пропускаем все единички в маске, копируя их на "волну" маски
с большим радиусом.
Метод получился опять каким-то уж очень извращенным. Глаза уже слипаются, поэтому
пока - отбой.
=== первое приближение к решению ===
Итак, сегодня я накачал всяких статеек. Почитал. Обычный алгоритм выделения
связанных областей (как я мог о нем не вспомнить?) жутко тормозной: он требует
как минимум четыре прохода (1 - маркировка соседних областей, 2 - построение
списка эквивалентности номеров, 3 - переименование областей, 4 - выделение
объектов). При этом на первом шаге мы должны проверять, были ли до пикселя нули,
на втором - верхнего соседа (и его боковых, если нужно заполнить 8-связанную
область), на третьем - сверяться с таблицей, на четвертом -
многократно сканировать изображение, пока все объекты не будут выделены.
Конечно, четвертый шаг можно укоротить: на третьем же шаге выделить массив
прямоугольников и обновлять размеры соответствующего прямоугольника.
Тогда четвертый шаг сведется к сканированию внутренностей каждого прямоугольника
с заполнением его маски единицами там, где в массиве номеров пиксель == N.
Процедура перемаркировки такова:
проходя матрицу с маркерами, проверяем каждый пиксель. Если пиксель нулевой,
устанавливаем флаг обнаружения в false и продолжаем. Если флаг == true, это значит,
что данный номер мы уже перемаркировали - продолжаем.
Если же пиксель - "свеженайденный", надо проверить все 6 пикселей (для 8-связных
областей) в предыдущем и последующем рядах (снизу - т.к. возможен вариант коротенького
кусочка, а то и единичного пикселя над длинной линией - из-за флага обнаружения мы
пролетим под ним, а вот на этапе сканирования его, изучая то, что под ним, сделаем
правильную ремаркировку). Вполне возможно, что нет необходимости проверять сразу и
пиксель над, и под нашим: пиксель точно под ним по-любому будет проверен (он - либо
часть более длинной линии, которую мы обнаружим в DL или DR углах, либо часть
вертикальной, которая обнаружится при сканировании нижней линии).
А вообще, полезно рассмотреть, какие варианты расположения соседей
могут быть, чтобы не пропустить мелочей (нумерация не важна, поэтому просто 1/0;
кроме того, т.к вариантов аж 256, рассмотрим лишь некоторые):
(X - что угодно, U - единица в одном из пикселей, V - единица в одном и более пикселей)
A B C D E F G H I J K L M N O P Q R
XXX 000 UUU 000 101 101 11X 000 010 000 000 000 000 000 000 000 000 000
11X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X 01X
XXX 000 000 VVV 000 VVV VVV 010 000 000 000 000 000 000 000 000 000 000
A - просто проходим мимо
B - инкрементируем счетчик уникальных объектов и перемаркируем текущий номер
C - ремаркируем текущее значение на то, которое содержится в массиве с индексом,
имеющим значение счетчика в пикселе с единичкой
D - то же, что в B + ремаркировка значений в нижней строке
E - ремаркировка текущего + правого верхнего счетчика по массиву с индексом,
равным значению счетчика в левом верхнем пикселе
F - ремаркировка всех по левому верхнему
G - для упрощения можно сделать эквивалентом F: нет нужды усложнять алгоритм ради
пары лишних операций
H - нет нужды проверять этот вариант, т.к. на стадии I он ремаркируется
I - заменяет H
Итак, общие тенденции:
- Если в верхней строке ничего нет, проверяем, не является ли наше текущее
значение счетчика уже перемаркированным: если является, оставляем как есть,
иначе - счетчик уникальных объектов инкрементируется и текущее значение перемаркируется
им; объекты внизу (если есть) тоже перемаркируются им
- если в левом верхнем углу !=0, все номера перемаркируются им, а пиксель над
нами проверять нет нужды
- если обнаружен !=0 ровно над нами, правый верхний проверять нет нужды, а оставшиеся
перемаркируются им
При организации процедуры переименования я натолкнулся на следующее: если ряд
X переименовывается в нижележащий Y, а потом Y переименовывается в Z, то ряд
X так и остается переименованным в Y.
Следовательно, при переименовании в массиве ассоциаций на стадии перенумерования
надо проверять, выполняется ли равенство assoc[assoc[X]] == assoc[X], т.е. зафиксировать
сложные связи.
Оказалось не все так просто с переименованием: надо будет нарисовать на бумажке,
что-то засыпаю уже. На сегодняшний день ничего не планирую (спать надо лечь рано,
т.к. завтра в 5 утра вставать), так что, пожалуй, закруглюсь и буду спать.
Сегодня еще вопросы с криостатом утрясти надо будет.
Пока засыпал, придумал, как правильно скорректировать алгоритм переименования:
скажем, переименовываем мы пиксель с изначальной меткой "X", имеющий новую
метку "Y" (т.е. assoc[X] != X) в "Z". В этом случае надо наоборот переименовать
Z в Y, а величины, больше Z, но <= Ncur декрементировать, затем декрементировать
и Ncur.
Все довольно просто, а очевидным
плюсом является то, что не надо многократно сканировать изображение для переименования
"по месту": это будет сделано на шаге 3.
Конечно, все познается в сравнении, поэтому сразу обливать дерьмом этот метод
нельзя: стоит попытаться его реализовать. А учитывая то, что все шаги, кроме второго,
превосходно параллелизуются (1, 3 - хоть по строкам, хоть по прямоугольным областям,
4 - по номерам объектов), на куде, пожалуй, он будет работать просто превосходно!
== еще мысли ==
Однако, меня все еще грызут сомнения: ведь по сути обнаружение связанных областей
подобно закрашиванию области, ограниченной контуром (в данном случае - "закрашивание"
нулями этой самой связанной области с "перенесением" соответствующих пикселей
во временное хранилище (а оттуда уже, по завершению закрашивания, - в соответствующее
подызображение).
Итак, если выбрать эффективный метод закрашивания областей, то обнаружение
сведется к следующему:
1. пробегаемся по точкам изображения, как только находим 1, "закрашиваем" всю
область нулями с "перенесением" ее во временную маску (равную размеру изображения)
и параллельным обновлением границ бокса;
2. выделяем для бокса память и переносим в него кусок из маски ("модифицированный"
шаг 4 из предыдущих размышлений);
3. сканируем изображение, начиная со следующей точки после точки из п.1.
Если опустить "волшебную" фразу "закрашиваем" из п.1, то изображение сканируется
не "три с половиной", а "полтора" раза. Т.о., если "закрашивание" будет
сканировать пиксели изображения лишь один раз, то получим весомый выигрыш.
Однако, тут же всплывает и минус: параллелится эта задача только разбиением
изображения на фреймы с последующим объединением оных. В принципе, если "закрашивание"
будет достаточно быстрым, то проблем не будет: все равно алгоритм будет работать
быстрее классического.
Итак, теперь остается прикинуть: что же за алгоритмы закрашивания есть.
Если верить википедии, то самый быстрый алгоритм основывается на закрашивании
области линиями с параллельной проверкой соседних сверху и снизу пикселей и
занесением в список координат пикселей, которые появляются после 0. По окончанию
сканирования линии, мы переходим к следующему пикселю в списке. И так до тех
пор, пока список не кончится. Причем, сканирование идет в двух направлениях!
Еще я наткнулся на какой-то "очень быстрый метод" маркирования связанных областей
от братьев-китайцев, но что-то даже после трехкратного прочтения я так и не понял:
то ли братья-китайцы что-то темнят, то ли я - идиот (конечно, возможны оба
варианта, но мне больше первый нравится).
Итак, хватит растекаться мысью по древу - пора переключаться в "медленный режим"
и реализовывать методы. В любом случае, закрашивание областей реализовать надо
(для начала - хотя бы 4-связанных, а о восьмисвязанных подумать, т.к. я что-то
с трудом себе представляю реальные объекты, ограниченные четким 4-связанным
контуром), поэтому стоит для начала на CPU с помощью openMP реализовать названные
два способа маркировки, да и сравнить их: даже если окажется, что второй способ
медленнее, он все равно понадобится для закрашивания.
// что-то у меня промежуточные всякие выкладки много времени занимают: сейчас вот
начал организовывать FIFO и подумал, что неплохо было бы туда и LIFO добавить...
== реализация перемаркировки ==
Реализуя перемаркировку, я наткнулся на кое-какие "косяки". В итоге "малюсенькая"
inline-функция для перемаркировки превратилась в громоздкого монстра.
Но надо еще протестировать на разных "картинках" ее поведение. Если проблем
с перемаркировкой не будет, можно будет переходить к завершающему этапу - заполнению.
Так как в процессе перемаркировки счетчик количества объектов скачет туда-сюда,
для завершающего - четвертого - этапа придется-таки использовать "полтора" прохода:
сначала пробежаться по изображению с заполнением величин соответствующих боксов,
а затем уже пробежаться по внутренностям каждого бокса, заполняя их маски.
Пожалуй, цикл по ремаркировке стоит вынести в отдельную функцию: тогда можно будет
искать и 4-связанные области без предварительной 4-фильтрации.
Проверка на случайно сгенерированных "картинках" показала, что алгоритм вроде бы
вполне рабочий. Надо бы добавить отбрасывание одиночных точек. Но что-то не
хочется мне утяжелять и без того уже тормозной и раздувшийся алгоритм.
Проще все-таки, наверное, сделать предварительную фильтрацию (по октетам)
"сжатого" изображения (как в FCfilter), а потом уже обрабатывать ее результат.
Кстати, сейчас прогнал поиск 8-связанных областей по изображению, отфильтрованному
функцией FCfilter, и до меня дошло, что все равно нужно писать отдельную функцию
поиска 4-связанных областей, т.к. вполне может быть так, что две 4-связанные
области соприкасаются по диагонали. При этом они являются разными объектами, но
поиск 8-связанных областей обзывает их одинаково (что понятно).
Для упрощения громоздких выкладок я сделал такой велосипед: внутренности некоторых
функций были вынесены в отдельные (многократно подключаемые) файлы. Перед подключением
файла для модификации устанавливается тот или иной флаг. В результате некоторое
количество условных операций отпадает. Правда, реализация перемаркировки
получилась у меня такой страшнючей, что мне лень ее оптимизировать подобным образом.
Перед тем, как выполнить поиск 4-связанных областей, я выполняю фильтрацию. Она
заодно отсекает и одиночные пиксели. А вот фильтрация для функции выделения
8-связанных областей заключается в том, что как раз только одиночные пиксели
и отсекаются. Жалею сейчас, что пока был в ИПФ, не нагуглил, как наиболее оптимально
их фильтровать, поэтому опять буду изобретать велосипед.
Фильтрация одиночных точек на "упакованном" изображении при помощи логических
операций, как я говорил выше, использует огромное количество промежуточных
вычислений, поэтому я даже и браться не буду за нее: выигрыш производительности
получится сомнительным. Поэтому, пожалуй, проще всего будет воткнуть эту проверку
в функцию поиска связанных областей.
Красотищща! Оно работает!!!
Рано радовался: запустил тест (случайная картинка 2000х2000): получилось, что
cc4 работает почти в 2 раза медленней, чем cc8!!! Как? А ведь у cc8 значительно
больше проверок... Черт знает что! Да и вообще не понравилось мне время
выполнения: 13.3 секунд у cc4 и 7.8 у cc8. Кстати, я еще не реализовал
шаг 4! При этом если из cc4 выкинуть функцию `FC_filter`, производительность
возрастает еще на пару секунд (ну, это,
наверное, из-за того, что приходится лишние пиксели проверять).
Я подозреваю, что виновата перемаркировка, занимающая тем больше времени,
чем больше областей обнаружено на изображении. Сейчас воткну тайминги в cclabel.
Тайминги показали, что действительно шаг 2 занимает на 4 порядка больше времени,
чем остальные шаги. А ведь сканирование-то идет единожды!
Отключив функцию ремаркировки, я получил крайне высокую производительность.
Глянул количество первично обнаруженных областей - их больше 200 тысяч!
Понятное дело, что если каждый раз при перемаркировке пробегаться по 200000
значений, а ремаркировок получаем почти столько же, производительность выйдет
вообще никакая!
В общем, надо думать, как оптимизировать это узкое место. Параллелизация
дала прирост производительности всего лишь в полтора раза. Я, правда, еще заметил,
что у меня количество итераций постоянно - по всем элементам, хотя стоит
проверять лишь те, которые уже пройдены. Скорректировал, получил прирост
производительности всего-то в полтора раза у cc4 и никакого прироста у cc8!
Уменьшив содержание единиц и нулей на изображении от 50/50 до 10/90, я получил
поразительно низкие цифры для cc4: полторы сотни миллисекунд! Время cc8 получилось
в районе 1с. Даже не знаю, нормально ли это: надо будет сравнить с другими
алгоритмами (хотя бы с лептоникой).
Уже сравнение с октавой показало, что у меня полная лажа: для тех же 50/50
октава дает 20мс! Зато для 10/90 у меня быстрей, чем в октаве (но октава
считает и одиночные точки :( ).
Проверка на очень большом количестве единиц показала, что считать все-таки надо
не до текущего значения (я про цикл с перемаркировкой) ведь нижняя строчкае
уже может быть ремаркирована -> получаем косячок-с. Чтобы его не было, добавлю
"про запас" половину ширины строки (больше объектов все равно быть не может).
Я долго думал, как бы это на графах реализовать, но что-то так и не придумал.
Посмотрел на время - уже начало 11-го. Пора баиньки, а то завтра в 4:30 вставать.
== другой метод ==
Пока сидел в аэропорту НН, было время почитать статью китайцев. Действительно,
интересный способ: мой метод (теоретически) будет хорош на небольшом количестве
больших объектов, а их метод - на большом количестве малых.
Принцип почти тот же, что и в моем случае, но:
- шаги 1 и 2 объединены: на стадии именования меток происходит и ремаркировка;
- второй проход (который у меня - третий) такой же, как у меня - переименование
участков по скорректированным меткам.
Правда, китайцы умолчали, как они создавали список меток: если его создавать
динамически (как список, динамический массив или даже дерево), то все операции
над списком будут довольно-таки дорогими, поэтому есть смысл просто выделить
заранее достаточное количество памяти. Учитывая то, что объектов на изображении
ну никак не может быть больше, чем 1/6 размера изображения, можно "на авось"
выделить 1/4 размера изображения под массив ремаркировок.
Еще одним отличием является метод сканирования: сканируются все пиксели, но
в зависимости от того, был ли ненулевой пиксель до текущего, или его не было,
производятся разные действия: если точка новая, производится проверка ТРЕХ точек
над ней (точки снизу не проверяются, понятное дело, т.к. происходит сканирование
всех точек) с соответствующим именованием текущей точки и ремаркировкой, если мы
натыкаемся на ситуацию, когда над точкой ничего нет, а вверху справа и слева -
разные метки. Если же до текущей точки уже была точка, нам нужно лишь проверить,
что находится в правом верхнем углу. Если там ничего нет, мы просто даем точке
номер соседней слева, если же есть - все равно даем тот же номер, но еще и производим
процедуру ремаркировки.
Благодаря такому методу сканирования (теоретически) повышается скорость: ремаркировки
необходимо производить значительно реже. В общем, надо бы проверить этот метод.
Описанная выше процедура - для маркировки 8-связанных областей, если же нам нужно
маркировать 4-связанные области, мы просто проверяем лишь точку НАД нашей.
Да уж, попытался я было что-нибудь "покодить", сидя в аэропорту. Оказалось, что это
не так-то и просто: без мыши и нормальной клавиатуры ну совсем невозможно работать!
Да еще и экран ноутбука перевешивает - бук так и норовит кувыркнуться с колен.
В общем, откладываю до дома.
Черт бы подрал этот аэропорт: wifi как бы номинально есть, а реально - не работает,
собака! Вроде бы соединяет, но скорость очень близка к нулю: tracepath дает
аж секунды! В общем, на втором этаже жизни нет.
Все-таки сделал более-менее реализацию. Работает, но нужно еще внимательно
просмотреть функцию ремаркировки: эта собака неправильно считает количество
найденных объектов!
Бульбец! Самолет задерживают на полтора часа! Буду дописывать...

428
erosion-dilation/binmorph.c Normal file
View File

@@ -0,0 +1,428 @@
/*
* binmorph.c - functions for morphological operations on binary image
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/*
TEST (10000 dilations / erosions / substitutions) for i5-3210M CPU @ 2.50GHz x2cores x2hyper
1. no openmp:
* 28.8 / 29.2 / 14.8
2. 2 threads:
* 17.0 / 17.9 / 8.8
3. 4 threads:
* 17.0 / 17.5 / 8.8
4. 8 threads:
* 17.0 / 17.7 / 8.9
*
option -O3 gives for nthreads = 4:
* 6.9 / 6.9 / 2.9
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <err.h>
#include <sys/time.h>
#include "binmorph.h"
//#include "fifo.h"
#ifdef OMP
#ifndef OMP_NUM_THREADS
#define OMP_NUM_THREADS 4
#endif
#define Stringify(x) #x
#define OMP_FOR(x) _Pragma(Stringify(omp parallel for x))
#else
#define OMP_FOR(x)
#endif
// global arrays for erosion/dilation masks
unsigned char *ER = NULL, *DIL = NULL;
bool __Init_done = false; // == true if arrays are inited
/*
* =================== AUXILIARY FUNCTIONS ===================>
*/
/**
* function for different purposes that need to know time intervals
* @return double value: time in seconds
*/
double dtime(){
double t;
struct timeval tv;
gettimeofday(&tv, NULL);
t = tv.tv_sec + ((double)tv.tv_usec)/1e6;
return t;
}
/**
* Memory allocation with control
* @param N - number of elements
* @param S - size of one element
* @return allocated memory or die
*/
void *my_alloc(size_t N, size_t S){
void *p = calloc(N, S);
if(!p) err(1, "malloc");
return p;
}
/**
* This function inits masks arrays for erosion and dilation
* You may call it yourself or it will be called when one of
* `erosion` or `dilation` functions will be ran first time
*/
void morph_init(){
if(__Init_done) return;
int i;//,j;
//bool a[8], b[8], c[9];
ER = Malloc(256, 1);
DIL = Malloc(256, 1);
for(i = 0; i < 256; i++){
/*ER[i] = DIL[i] = 0;
for(j = 0; j < 8; j++){
a[j] = (i >> j) & 1;
b[j] = a[j];
c[j] = a[j];
}
for(j = 1; j < 7; j++)
if(!a[j])
b[j+1] = b[j-1]= false;
else
c[j] = c[j-1] = c[j+1] = true;
b[1] &= a[0]; b[6] &= a[7];
c[1] |= a[0]; c[6] |= a[7];
for(j = 0; j < 8; j++){
ER[i] |= b[j] << j;
DIL[i] |= c[j] << j;
}
*/
ER[i] = i & ((i << 1) | 1) & ((i >> 1) | (0x80)); // don't forget that << and >> set borders to zero
DIL[i] = i | (i << 1) | (i >> 1);
}
__Init_done = true;
}
/**
* Print out small "packed" images as matrix of "0" and "1"
* @param i (i) - image
* @param W, H - size of image
*/
void printC(unsigned char *i, int W, int H){
int x,y,b;
for(y = 0; y < H; y++){
for(x = 0; x < W; x++, i++){
unsigned char c = *i;
for(b = 0; b < 8; b++)
printf("%c", c << b & 0x80 ? '1' : '0');
}
printf("\n");
}
}
/**
* Print out small "unpacked" images as almost equiaxed matrix of ".." and "##"
* @param img (i) - image
* @param W, H - size of image
*/
void printB(bool *img, int W, int H){
int i, j;
for(j = 0; j < W; j++)
if(j % 10 == 1){ printf("%02d", j-1); printf(" ");}
printf("\n");
for(i = 0; i < H; i++){
printf("%2d ",i);
for(j = 0; j < W; j++)
printf("%s", (*img++) ? "##" : "..");
printf("\n");
}
}
/*
* <=================== AUXILIARY FUNCTIONS ===================
*/
/*
* =================== CONVERT IMAGE TYPES ===================>
*/
/**
* Convert boolean image into pseudo-packed (1 char == 8 pixels)
* @param im (i) - image to convert
* @param W, H - size of image im (must be larger than 1)
* @param W_0 (o) - (stride) new width of image
* @return allocated memory area with "packed" image
*/
unsigned char *bool2char(bool *im, int W, int H, int *W_0){
if(W < 2 || H < 2) errx(1, "bool2char: image size too small");
int y, W0 = (W + 7) / 8;
unsigned char *ret = Malloc(W0 * H, 1);
OMP_FOR()
for(y = 0; y < H; y++){
int x, i, X;
bool *ptr = &im[y*W];
unsigned char *rptr = &ret[y*W0];
for(x = 0, X = 0; x < W0; x++, rptr++){
for(i = 7; i > -1 && X < W; i--, X++, ptr++){
*rptr |= *ptr << i;
}
}
}
if(W_0) *W_0 = W0;
return ret;
}
/**
* Convert "packed" image into boolean
* @param image (i) - input image
* @param W, H, W_0 - size of image and width of "packed" image
* @return allocated memory area with "unpacked" image
*/
bool *char2bool(unsigned char *image, int W, int H, int W_0){
int y;
bool *ret = Malloc(W * H, sizeof(bool));
OMP_FOR()
for(y = 0; y < H; y++){
int x, X, i;
bool *optr = &ret[y*W];
unsigned char *iptr = &image[y*W_0];
for(x = 0, X = 0; x < W_0; x++, iptr++)
for(i = 7; i > -1 && X < W; i--, X++, optr++){
*optr = (*iptr >> i) & 1;
}
}
return ret;
}
/**
* Convert "packed" image into size_t array for conncomp procedure
* @param image (i) - input image
* @param W, H, W_0 - size of image and width of "packed" image
* @return allocated memory area with copy of an image
*/
size_t *char2st(unsigned char *image, int W, int H, int W_0){
int y;
size_t *ret = Malloc(W * H, sizeof(size_t));
OMP_FOR()
for(y = 0; y < H; y++){
int x, X, i;
size_t *optr = &ret[y*W];
unsigned char *iptr = &image[y*W_0];
for(x = 0, X = 0; x < W_0; x++, iptr++)
for(i = 7; i > -1 && X < W; i--, X++, optr++){
*optr = (*iptr >> i) & 1;
}
}
return ret;
}
/*
* <=================== CONVERT IMAGE TYPES ===================
*/
/*
* =================== MORPHOLOGICAL OPERATIONS ===================>
*/
/**
* Remove all non-4-connected pixels
* @param image (i) - input image
* @param W, H - size of image
* @return allocated memory area with converted input image
*/
unsigned char *FC_filter(unsigned char *image, int W, int H){
if(W < 2 || H < 2) errx(1, "4-connect: image size too small");
unsigned char *ret = Malloc(W*H, 1);
int y = 0, w = W-1, h = H-1;
// top of image, y = 0
#define IM_UP
#include "fc_filter.h"
#undef IM_UP
// mid of image, y = 1..h-1
#include "fc_filter.h"
// image bottom, y = h
y = h;
#define IM_DOWN
#include "fc_filter.h"
#undef IM_DOWN
return ret;
}
/**
* Make morphological operation of dilation
* @param image (i) - input image
* @param W, H - size of image
* @return allocated memory area with dilation of input image
*/
unsigned char *dilation(unsigned char *image, int W, int H){
if(W < 2 || H < 2) errx(1, "Dilation: image size too small");
if(!__Init_done) morph_init();
unsigned char *ret = Malloc(W*H, 1);
int y = 0, w = W-1, h = H-1;
// top of image, y = 0
#define IM_UP
#include "dilation.h"
#undef IM_UP
// mid of image, y = 1..h-1
#include "dilation.h"
// image bottom, y = h
y = h;
#define IM_DOWN
#include "dilation.h"
#undef IM_DOWN
return ret;
}
/**
* Make morphological operation of erosion
* @param image (i) - input image
* @param W, H - size of image
* @return allocated memory area with erosion of input image
*/
unsigned char *erosion(unsigned char *image, int W, int H){
if(W < 2 || H < 2) errx(1, "Erosion: image size too small");
if(!__Init_done) morph_init();
unsigned char *ret = Malloc(W*H, 1);
int y, w = W-1, h = H-1;
OMP_FOR()
for(y = 1; y < h; y++){ // reset first & last rows of image
unsigned char *iptr = &image[W*y];
unsigned char *optr = &ret[W*y];
unsigned char p = ER[*iptr] & 0x7f & iptr[-W] & iptr[W];
int x;
if(!(iptr[1] & 0x80)) p &= 0xfe;
*optr++ = p;
iptr++;
for(x = 1; x < w; x++, iptr++, optr++){
p = ER[*iptr] & iptr[-W] & iptr[W];
if(!(iptr[-1] & 1)) p &= 0x7f;
if(!(iptr[1] & 0x80)) p &= 0xfe;
*optr = p;
}
p = ER[*iptr] & 0xfe & iptr[-W] & iptr[W];
if(!(iptr[-1] & 1)) p &= 0x7f;
*optr++ = p;
iptr++;
}
return ret;
}
/*
* <=================== MORPHOLOGICAL OPERATIONS ===================
*/
/*
* =================== LOGICAL OPERATIONS ===================>
*/
/**
* Logical AND of two images
* @param im1, im2 (i) - two images
* @param W, H - their size (of course, equal for both images)
* @return allocated memory area with image = (im1 AND im2)
*/
unsigned char *imand(unsigned char *im1, unsigned char *im2, int W, int H){
unsigned char *ret = Malloc(W*H, 1);
int y;
OMP_FOR()
for(y = 0; y < H; y++){
int x, S = y*W;
unsigned char *rptr = &ret[S], *p1 = &im1[S], *p2 = &im2[S];
for(x = 0; x < W; x++)
*rptr++ = *p1++ & *p2++;
}
return ret;
}
/**
* Substitute image 2 from image 1: reset to zero all bits of image 1 which set to 1 on image 2
* @param im1, im2 (i) - two images
* @param W, H - their size (of course, equal for both images)
* @return allocated memory area with image = (im1 AND (!im2))
*/
unsigned char *substim(unsigned char *im1, unsigned char *im2, int W, int H){
unsigned char *ret = Malloc(W*H, 1);
int y;
OMP_FOR()
for(y = 0; y < H; y++){
int x, S = y*W;
unsigned char *rptr = &ret[S], *p1 = &im1[S], *p2 = &im2[S];
for(x = 0; x < W; x++)
*rptr++ = *p1++ & (~*p2++);
}
return ret;
}
/*
* <=================== LOGICAL OPERATIONS ===================
*/
/*
* =================== CONNECTED COMPONENTS LABELING ===================>
*/
/*
* <=================== CONNECTED COMPONENTS LABELING ===================
*/
/*
#undef DBG
#undef EBUG
#define DBG(...)
*/
/**
* label 4-connected components on image
* (slow algorythm, but easy to parallel)
*
* @param I (i) - image ("packed")
* @param W,H,W_0 - size of the image (W - width in pixels, W_0 - width in octets)
* @param Nobj (o) - number of objects found
* @return an array of labeled components
*/
CCbox *cclabel4(unsigned char *Img, int W, int H, int W_0, size_t *Nobj){
unsigned char *I = FC_filter(Img, W_0, H);
#include "cclabling.h"
FREE(I);
return ret;
}
// label 8-connected components, look cclabel4
CCbox *cclabel8(unsigned char *I, int W, int H, int W_0, size_t *Nobj){
//unsigned char *I = EC_filter(Img, W_0, H);
#define LABEL_8
#include "cclabling.h"
#undef LABEL_8
// FREE(I);
return ret;
}
CCbox *cclabel8_1(unsigned char *I, int W, int H, int W_0, size_t *Nobj){
#define LABEL_8
#include "cclabling_1.h"
#undef LABEL_8
return ret;
}
/*
* <=================== template ===================>
*/

View File

@@ -0,0 +1,86 @@
/*
* binmorph.h
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#pragma once
#ifndef __EROSION_DILATION_H__
#define __EROSION_DILATION_H__
#include <stdbool.h>
#include <stdlib.h>
#ifdef EBUG
#ifndef DBG
#define DBG(...) do{fprintf(stderr, __VA_ARGS__);}while(0)
#endif
#endif
#define _U_ __attribute__((__unused__))
#ifndef Malloc
#define Malloc my_alloc
#endif
#ifndef FREE
#define FREE(arg) do{free(arg); arg = NULL;}while(0)
#endif
#ifndef _
#define _(X) X
#endif
// auxiliary functions
void *my_alloc(size_t N, size_t S);
double dtime();
void printC(unsigned char *i, int W, int H);
void printB(bool *i, int W, int H);
void morph_init(); // there's no need to run it by hands, but your will is the law
// convert image types
unsigned char *bool2char(bool *im, int W, int H, int *stride);
bool *char2bool(unsigned char *image, int W, int H, int W_0);
size_t *char2st(unsigned char *image, int W, int H, int W_0);
// morphological operations
unsigned char *dilation(unsigned char *image, int W, int H);
unsigned char *erosion(unsigned char *image, int W, int H);
unsigned char *FC_filter(unsigned char *image, int W, int H);
// logical operations
unsigned char *imand(unsigned char *im1, unsigned char *im2, int W, int H);
unsigned char *substim(unsigned char *im1, unsigned char *im2, int W, int H);
// conncomp
// this is a box structure containing one object; data is aligned by original image bytes!
typedef struct {
unsigned char *data; // pattern data in "packed" format
int x, // x coordinate of LU-pixel of box in "unpacked" image (by pixels)
y, // y -//-
x_0; // x coordinate in "packed" image (morph operations should work with it)
size_t N;// number of component, starting from 1
} CCbox;
CCbox *cclabel4(unsigned char *I, int W, int H, int W_0, size_t *Nobj);
CCbox *cclabel8(unsigned char *I, int W, int H, int W_0, size_t *Nobj);
CCbox *cclabel8_1(unsigned char *I, int W, int H, int W_0, size_t *Nobj);
#endif // __EROSION_DILATION_H__

View File

@@ -0,0 +1,204 @@
/*
* cclabling.h - inner part of functions cclabel4 and cclabel8
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
double t0 = dtime();
CCbox *ret = NULL;
size_t N _U_ = 0, Ncur = 0;
size_t *labels = char2st(I, W, H, W_0);
int y;
#ifdef EBUG
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
if(*ptr) printf("%02zx", *ptr);
else printf(" ");
}
printf("\n");
}
FREE(labels); return ret;
#endif
printf("time for char2st: %gs\n", dtime()-t0);
t0 = dtime();
// Step 1: mark neighbours by strings
size_t *ptr = labels;
for(y = 0; y < H; y++){
bool found = false;
for(int x = 0; x < W; x++, ptr++){
if(!*ptr){ found = false; continue;}
if(!found){
found = true;
++Ncur;
}
*ptr = Ncur;
}
}
printf("\n\ntime for step1: %gs (Ncur=%zd)\n", dtime()-t0, Ncur);
t0 = dtime();
DBG("Initial mark\n");
#ifdef EBUG
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
if(*ptr) printf("%02zx", *ptr);
else printf(" ");
}
printf("\n");
}
#endif
// Step 2: fill associative array with remarking
DBG("remarking\n");
N = Ncur+1; // size of markers array (starting from 1)
size_t i, *assoc = Malloc(N, sizeof(size_t));
for(i = 0; i < N; i++) assoc[i] = i; // primary initialisation
// now we should scan image again to rebuild enumeration
// THIS PROCESS AVOID PARALLELISATION???
Ncur = 0;
size_t h = H-1
#ifdef LABEL_8
,w = W-1;
#endif
;
inline void remark(size_t old, size_t *newv){
size_t new = *newv;
if(assoc[old] == new){
DBG("(%zd)\n", new);
return;
}
DBG("[%zx], %zx -> %zx ", Ncur, old, new);
if(assoc[old] == old){ // remark non-remarked value
assoc[old] = new;
DBG("\n");
return;
}
// value was remarked -> we have to remark "new" to assoc[old]
// and decrement all marks, that are greater than "new"
size_t ao = assoc[old];
DBG("(remarked to %zx) ", assoc[old]);
// now we must check assoc[old]: if it is < new -> change *newv, else remark assoc[old]
if(ao < new)
*newv = ao;
else{
size_t x = ao; ao = new; new = x; // swap values
}
int xx = old + W / 2;
if(xx > N) xx = N;
OMP_FOR()
for(int i = 1; i <= xx; i++){
size_t m = assoc[i];
if(m < new) continue;
if(m == new){
assoc[i] = ao;
DBG("remark %x (%zd) to %zx ", i, m, ao);
}else if(m <= Ncur){
assoc[i]--;
DBG("decr %x: %zx, ", i, assoc[i]);
}
}
DBG("\n");
Ncur--;
}
for(y = 0; y < H; y++){ // FIXME!!!! throw out that fucking "if" checking coordinates!!!
size_t *ptr = &labels[y*W];
bool found = false;
for(int x = 0; x < W; x++, ptr++){
size_t curval = *ptr;
if(!curval){ found = false; continue;}
if(found) continue; // we go through remarked pixels
found = true;
DBG("curval: %zx ", curval);
// now check neighbours in upper and lower string:
size_t *U = (y) ? &ptr[-W] : NULL;
size_t *D = (y < h) ? &ptr[W] : NULL;
size_t upmark = 0; // mark from line above
if(U){
#ifdef LABEL_8
if(x && U[-1]){ // there is something in upper left corner -> make remark by its value
upmark = assoc[U[-1]];
}else // check point above only if there's nothing in left up
#endif
if(U[0]) upmark = assoc[U[0]];
#ifdef LABEL_8
if(x < w) if(U[1]){ // there's something in upper right
if(upmark){ // to the left of it was pixels
remark(U[1], &upmark);
}else
upmark = assoc[U[1]];
}
#endif
}
if(!upmark){ // there's nothing above - set current pixel to incremented counter
#ifdef LABEL_8 // check, whether pixel is not single
if( !(x && ((D && D[-1]) || ptr[-1])) // no left neighbours
&& !(x < w && ((D && D[1]) || ptr[1])) // no right neighbours
&& !(D && D[0])){ // no neighbour down
*ptr = 0; // erase this hermit!
continue;
}
#endif
upmark = ++Ncur;
}
// now remark DL and DR corners (bottom pixel will be checked on next line)
if(D){
#ifdef LABEL_8
if(x && D[-1]){
remark(D[-1], &upmark);
}
if(x < w && D[1]){
remark(D[1], &upmark);
}
#else
if(D[0]) remark(D[0], &upmark);
#endif
}
// remark current
remark(curval, &upmark);
}
}
printf("time for step 2: %gs, found %zd objects\n", dtime()-t0, Ncur);
t0 = dtime();
// Step 3: rename markers
DBG("rename markers\n");
// first correct complex assotiations in assoc
OMP_FOR()
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
size_t p = *ptr;
if(!p){continue;}
*ptr = assoc[p];
}
}
printf("time for step 3: %gs\n", dtime()-t0);
#ifdef EBUG
printf("\n\n");
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
if(*ptr) printf("%02zx", *ptr);
else printf(" ");
}
printf("\n");
}
#endif
FREE(assoc);
FREE(labels);

View File

@@ -0,0 +1,170 @@
/*
* cclabling_1.h - inner part of functions cclabel4_1 and cclabel8_1
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
double t0 = dtime();
CCbox *ret = NULL;
size_t N = 0, // current label
Ntot = 0, // number of found objects
Nmax = W*H/4; // max number of labels
size_t *labels = char2st(I, W, H, W_0);
int w = W - 1;
printf("time for char2st: %gs\n", dtime()-t0);
int y;
size_t *assoc = Malloc(Nmax, sizeof(size_t)); // allocate memory for "remark" array
inline void remark(size_t old, size_t *newv){
size_t new = *newv;
if(old == new || assoc[old] == new || assoc[new] == old){
DBG("(%zd)\n", new);
return;
}
DBG("[cur: %zx, tot: %zd], %zx -> %zx ", N, Ntot, old, new);
if(!assoc[old]){ // try to remark non-marked value
assoc[old] = new;
DBG("\n");
return;
}
// value was remarked -> we have to remark "new" to assoc[old]
// and decrement all marks, that are greater than "new"
size_t ao = assoc[old];
DBG("(remarked to %zx) ", ao);
DBG(" {old: %zd, new: %zd, a[o]=%zd, a[n]=%zd} ", old, new, assoc[old], assoc[new]);
// now we must check assoc[old]: if it is < new -> change *newv, else remark assoc[old]
if(ao < new)
*newv = ao;
else{
size_t x = ao; ao = new; new = x; // swap values
}
int m = (old > new) ? old : new;
int xx = m + W / 2;
if(xx > Nmax) xx = Nmax;
DBG(" [[xx=%d]] ", xx);
OMP_FOR()
for(int i = 1; i <= xx; i++){
size_t m = assoc[i];
if(m < new) continue;
if(m == new){
assoc[i] = ao;
DBG("remark %x (%zd) to %zx ", i, m, ao);
}else{
assoc[i]--;
DBG("decr %x: %zx, ", i, assoc[i]);
}
}
DBG("\n");
Ntot--;
}
t0 = dtime();
size_t *ptr = labels;
for(y = 0; y < H; y++){ // FIXME!!!! throw out that fucking "if" checking coordinates!!!
bool found = false;
size_t curmark = 0; // mark of pixel to the left
for(int x = 0; x < W; x++, ptr++){
size_t curval = *ptr;
if(!curval){ found = false; continue;}
size_t *U = (y) ? &ptr[-W] : NULL;
size_t upmark = 0; // mark from line above
if(!found){ // new pixel, check neighbours above
found = true;
// now check neighbours in upper string:
if(U){
//#ifdef LABEL_8
if(x && U[-1]){ // there is something in upper left corner -> use its value
upmark = U[-1];
}else // check point above only if there's nothing in left up
//#endif
if(U[0]) upmark = U[0];
//#ifdef LABEL_8
if(x < w && U[1]){ // there's something in upper right
if(upmark){ // to the left of it was pixels
remark(U[1], &upmark);
}else
upmark = U[1];
}
//#endif
}
if(!upmark){ // there's nothing above - set current pixel to incremented counter
#ifdef LABEL_8 // check, whether pixel is not single
size_t *D = (y < w) ? &ptr[W] : NULL;
if( !(x && ((D && D[-1]) || ptr[-1])) // no left neighbours
&& !(x < w && ((D && D[1]) || ptr[1])) // no right neighbours
&& !(D && D[0])){ // no neighbour down
*ptr = 0; // erase this hermit!
continue;
}
#endif
upmark = ++N;
Ntot++;
assoc[upmark] = upmark; // refresh "assoc"
}
*ptr = upmark;
curmark = upmark;
//remark(curval, &upmark);
}else{ // there was something to the left -> we must chek only U[1]
if(U){
if(x < w && U[1]){ // there's something in upper right
remark(U[1], &curmark);
}
}
*ptr = curmark;
}
}
}
printf("time for step 2: %gs, found %zd objects\n", dtime()-t0, Ntot);
DBG("Initial mark\n");
#ifdef EBUG
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
if(*ptr) printf("%02zx", *ptr);
else printf(" ");
}
printf("\n");
}
#endif
t0 = dtime();
// Step 2: rename markers
DBG("rename markers\n");
// first correct complex assotiations in assoc
OMP_FOR()
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
size_t p = *ptr;
if(!p){continue;}
*ptr = assoc[p];
}
}
printf("time for step 3: %gs\n", dtime()-t0);
#ifdef EBUG
printf("\n\n");
for(y = 0; y < H; y++){
size_t *ptr = &labels[y*W];
for(int x = 0; x < W; x++, ptr++){
if(*ptr) printf("%02zx", *ptr);
else printf(" ");
}
printf("\n");
}
#endif
FREE(assoc);
FREE(labels);

View File

@@ -0,0 +1,75 @@
/*
* dilation.h - inner part of function `dilation`
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/*
* HERE'S NO ANY "FILE-GUARDS" BECAUSE FILE IS MULTIPLY INCLUDED!
* I use this because don't want to dive into depths of BOOST
*
* multi-including with different defines before - is a simplest way to
* modify simple code for avoiding extra if-then-else
*/
#if ! defined IM_UP && ! defined IM_DOWN // image without upper and lower borders
OMP_FOR()
for(y = 1; y < h; y++)
#endif
{
unsigned char *iptr = &image[W*y];
unsigned char *optr = &ret[W*y];
unsigned char p = DIL[*iptr]
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
int x;
if(iptr[1] & 0x80) p |= 1;
*optr = p;
optr++; iptr++;
for(x = 1; x < w; x++, iptr++, optr++){
p = DIL[*iptr]
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
if(iptr[1] & 0x80) p |= 1;
if(iptr[-1] & 1) p |= 0x80;
*optr = p;
}
p = DIL[*iptr]
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
if(iptr[-1] & 1) p |= 0x80;
*optr = p;
optr++; iptr++;
}

View File

@@ -0,0 +1,67 @@
/*
* fc_filter.h - inner part of function `FC_filter`
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
// The same shit as for dilation.h
#if ! defined IM_UP && ! defined IM_DOWN
OMP_FOR()
for(y = 1; y < h; y++)
#endif
{
unsigned char *iptr = &image[W*y];
unsigned char *optr = &ret[W*y];
unsigned char p = *iptr << 1 | *iptr >> 1
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
int x;
if(iptr[1] & 0x80) p |= 1;
*optr = p & *iptr;
optr++; iptr++;
for(x = 1; x < w; x++, iptr++, optr++){
p = *iptr << 1 | *iptr >> 1
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
if(iptr[1] & 0x80) p |= 1;
if(iptr[-1] & 1) p |= 0x80;
*optr = p & *iptr;
}
p = *iptr << 1 | *iptr >> 1
#ifndef IM_UP
| iptr[-W]
#endif
#ifndef IM_DOWN
| iptr[W]
#endif
;
if(iptr[-1] & 1) p |= 0x80;
*optr = p & *iptr;
optr++; iptr++;
}

View File

173
erosion-dilation/main.c Normal file
View File

@@ -0,0 +1,173 @@
/*
* main.c - test file for binmorph.c
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdio.h>
#include <math.h>
#include "binmorph.h"
// these includes are for randini
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
/*
* Generate a quasy-random number to initialize PRNG
* name: throw_random_seed
* @return value for curandSetPseudoRandomGeneratorSeed or srand48
*/
long throw_random_seed(){
//FNAME();
long r_ini;
int fail = 0;
int fd = open("/dev/random", O_RDONLY);
do{
if(-1 == fd){
fail = 1; break;
}
if(sizeof(long) != read(fd, &r_ini, sizeof(long))){
fail = 1;
}
close(fd);
}while(0);
if(fail){
double tt = dtime() * 1e6;
double mx = (double)LONG_MAX;
r_ini = (long)(tt - mx * floor(tt/mx));
}
return (r_ini);
}
int main(int ac, char **v){
int i,j, W = 28, H = 28, W_0;
//double midX = (W - 1.0)/ 4. - 1., midY = (H - 1.) / 4. - 1.;
//double ro = sqrt(midX*midY), ri = ro / 1.5;
bool *inp = Malloc(W * H, sizeof(bool));
printf("\n");
srand48(throw_random_seed());
for(i = 0; i < H; i++)
for(j = 0; j < W; j++)
inp[i*W+j] = (drand48() > 0.5);
/*
for(i = 0; i < H/2; i++){
for(j = 0; j < W/2; j++){
double x = j - midX, y = i - midY;
double r = sqrt(x*x + y*y);
if((r < ro && r > ri) || fabs(x) == fabs(y))
inp[i*W+j] = inp[i*W+j+W/2] = inp[(i+H/2)*W+j] = inp[(i+H/2)*W+j+W/2] = true;
}
}*/
unsigned char *b2ch = bool2char(inp, W, H, &W_0);
FREE(inp);
printf("inpt: (%dx%d)\n", W_0, H);
printC(b2ch, W_0, H);
unsigned char *eros = erosion(b2ch, W_0, H);
printf("erosion: (%dx%d)\n", W_0, H);
printC(eros, W_0, H);
unsigned char *dilat = dilation(b2ch, W_0, H);
printf("dilation: (%dx%d)\n", W_0, H);
printC(dilat, W_0, H);
unsigned char *erdilat = dilation(eros, W_0, H);
printf("\n\ndilation of erosion (openinfg: (%dx%d)\n", W_0, H);
printC(erdilat, W_0, H);
unsigned char *dileros = erosion(dilat, W_0, H);
printf("erosion of dilation (closing): (%dx%d)\n", W_0, H);
printC(dileros, W_0, H);
printf("\n\n\n image - opening of original minus original:\n");
unsigned char *immer = substim(b2ch, erdilat, W_0, H);
printC(immer, W_0, H);
FREE(eros); FREE(dilat); FREE(erdilat); FREE(dileros);
inp = char2bool(immer, W, H, W_0);
printf("\n\nAnd boolean for previous image (%dx%d):\n ",W,H);
printB(inp, W, H);
FREE(immer);
immer = FC_filter(b2ch, W_0, H);
printf("\n\n\nFilter for 4-connected areas searching:\n");
printC(immer, W_0, H);
FREE(immer);
size_t NL;
printf("\nmark 8-connected components:\n");
cclabel8_1(b2ch, W, H, W_0, &NL);
printf("\nmark 8-connected components (old):\n");
cclabel8(b2ch, W, H, W_0, &NL);
FREE(b2ch);
return 0;
W = H = 1000;
FREE(inp);
inp = Malloc(W * H, sizeof(bool));
for(i = 0; i < H; i++)
for(j = 0; j < W; j++)
inp[i*W+j] = (drand48() > 0.5);
b2ch = bool2char(inp, W, H, &W_0);
FREE(inp);
printf("\n\n\n1) 1 cc4 for 2000x2000 .. ");
fflush(stdout);
double t0 = dtime();
for(i = 0; i < 1; i++){
CCbox *b = cclabel4(b2ch, W, H, W_0, &NL);
FREE(b);
}
printf("%.3f s\n", dtime() - t0);
printf("\n2) 1 cc8 for 2000x2000 .. ");
fflush(stdout);
t0 = dtime();
for(i = 0; i < 1; i++){
CCbox *b = cclabel8(b2ch, W, H, W_0, &NL);
FREE(b);
}
printf("%.3f s\n", dtime() - t0);
printf("\n3) 1 cc8_1 for 2000x2000 .. ");
fflush(stdout);
t0 = dtime();
for(i = 0; i < 1; i++){
CCbox *b = cclabel8_1(b2ch, W, H, W_0, &NL);
FREE(b);
}
printf("%.3f s\n", dtime() - t0);
/* printf("\n\n\n\nSome tests:\n1) 10000 dilations for 2000x2000 .. ");
fflush(stdout);
double t0 = dtime();
for(i = 0; i < 10000; i++){
unsigned char *dilat = dilation(b2ch, W, H);
free(dilat);
}
printf("%.3f s\n", dtime() - t0);
printf("2) 10000 erosions for 2000x2000 .. ");
fflush(stdout);
t0 = dtime();
for(i = 0; i < 10000; i++){
unsigned char *dilat = erosion(b2ch, W, H);
free(dilat);
}
printf("%.3f s\n", dtime() - t0);
printf("3) 10000 image substitutions for 2000x2000 .. ");
fflush(stdout);
unsigned char *dilat = dilation(b2ch, W, H);
t0 = dtime();
for(i = 0; i < 10000; i++){
unsigned char *res = substim(b2ch, dilat, W, H);
free(res);
}
printf("%.3f s\n", dtime() - t0);*/
return 0;
}