mirror of
https://github.com/eddyem/eddys_snippets.git
synced 2026-03-20 08:41:02 +03:00
first commit
This commit is contained in:
19
erosion-dilation/Makefile
Normal file
19
erosion-dilation/Makefile
Normal 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
523
erosion-dilation/README
Normal 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
428
erosion-dilation/binmorph.c
Normal 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 ===================>
|
||||
*/
|
||||
86
erosion-dilation/binmorph.h
Normal file
86
erosion-dilation/binmorph.h
Normal 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__
|
||||
|
||||
|
||||
204
erosion-dilation/cclabling.h
Normal file
204
erosion-dilation/cclabling.h
Normal 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);
|
||||
170
erosion-dilation/cclabling_1.h
Normal file
170
erosion-dilation/cclabling_1.h
Normal 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);
|
||||
75
erosion-dilation/dilation.h
Normal file
75
erosion-dilation/dilation.h
Normal 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++;
|
||||
}
|
||||
|
||||
67
erosion-dilation/fc_filter.h
Normal file
67
erosion-dilation/fc_filter.h
Normal 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++;
|
||||
}
|
||||
0
erosion-dilation/fs_filter.h
Normal file
0
erosion-dilation/fs_filter.h
Normal file
173
erosion-dilation/main.c
Normal file
173
erosion-dilation/main.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user