本文实现参考matlab实现方式,将m语言转换成C实现。参考:https://ww2.mathworks.cn/help/images/ref/bwmorph.html
matlab中这样调用BW = bwmorph(BW1,‘shrink’,n);
matlab中这样描述shrink操作:With n = Inf, shrinks objects to points. It removes pixels so that objects without holes shrink to a point, and objects with holes shrink to a connected ring halfway between each hole and the outer boundary. This option preserves the Euler number.也即是当输入操作次数为无限时,shrink操作会将衣服二值图中物体区域变成单个的像素点。分为两种情况,一种是区域没洞时,区域变成一个点;另一种是区域有洞时,区域变成一个连接的环;此操纵保持平面欧拉数不变。
例如,
无洞情形:
0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 0 0 0 0 0 0 0
shrink操作之后变成:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
有洞情形:
0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 0 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 0 0 0 0 0 0 0
shrink操作之后变为:
0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 1 0 0
0 0 1 0 0 0 1 0
0 0 1 0 0 1 0 0
0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
matlab中的实现,已经进行整合:
首先获取shrink查找表:
function lut = lutshrink
%LUTSHRINK Compute "shrink" look-up table.
% Copyright 1993-2012 The MathWorks, Inc.
%#codegen
lut = logical([ ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 1 1 1 1 0 1 1 ...
1 1 1 1 0 0 1 1 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
1 0 1 1 1 0 1 1 0 0 1 1 ...
0 0 1 1 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 1 0 0 0 ...
0 0 0 0 1 1 1 1 0 0 1 1 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 1 1 0 0 1 1 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
1 0 0 0 0 0 0 0 1 1 1 1 ...
0 0 1 1 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 1 0 1 1 ...
1 0 1 1 1 1 0 0 1 1 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 1 0 0 0 0 0 0 0 ...
1 1 1 1 0 0 1 1 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
1 0 1 1 1 0 1 1 1 1 0 0 ...
1 1 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 1 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 1 0 1 1 1 0 1 1 ...
0 0 1 1 0 0 1 1 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 1 1 0 0 1 1 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 1 0 0 0 0 0 0 0 ...
1 1 1 1 0 0 1 1 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
1 0 1 1 1 0 1 1 1 1 0 0 ...
1 1 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 1 0 0 0 ...
0 0 0 0 1 1 1 1 0 0 1 1 ...
0 0 0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 1 0 1 1 1 0 1 1 ...
1 1 0 0 1 1 0 0])';
完成一次shrink操作,一次shrink操作为4次查表迭代,第一次处理奇数行,奇数列,注意matlab中以1作为行号或者列号的开始,第二次处理偶数行列,第三次奇数行、偶数列,第4次偶数行奇数列;
% First subiteration
m = bwlookup(bw, table);
sub = bw & ~m;
bw(1:2:end,1:2:end) = sub(1:2:end,1:2:end);
% Second subiteration
m = bwlookup(bw, table);
sub = bw & ~m;
bw(2:2:end,2:2:end) = sub(2:2:end,2:2:end);
% Third subiteration
m = bwlookup(bw, table);
sub = bw & ~m;
bw(1:2:end,2:2:end) = sub(1:2:end,2:2:end);
% Fourth subiteration
m = bwlookup(bw, table);
sub = bw & ~m;
bw(2:2:end,1:2:end) = sub(2:2:end,1:2:end);
从下面的matlab可以看出如果本次shrink的结果与上次的结果相同,或者shrink操作达到指定的次数,结束循环。
while (~done)
lastbw = bw;
bw = applylut(bw, lut);//调用一次shrink操作;
done = ((iter >= n) | isequal(lastbw, bw));
iter = iter + 1;
end
C实现:
shrink查找表,直接从matlab中拷贝过来,加上逗号,中括号改为花括号,C的数组形式:
static int shrink_table[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1,
1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 1, 1, 0, 0
};
从内存缓冲区中创建二值图像,相关数据结果可以参考前面的博客,
static uint8_2d* uint8_2d_create_from_data(uint8_t* data, int rows, int cols)
{
uint8_2d* res = NULL;
if (!data)return NULL;
res = create_uint8_2d(rows, cols);
if (!res)
{
return NULL;
}
memcpy(res->data, data, rows*cols);
return res;
判断两幅二值图像是否相等,需要二值图像的所有点相等:
static int uint8_2d_equal(uint8_2d* a, uint8_2d* b)
{
int res = 0;
if (!a || !b)return res;
int rows, cols;
int i, j;
uint8_t** a_arr;
uint8_t** b_arr;
assert(a->cols == b->cols && a->rows == b->rows);
rows = a->rows;
cols = b->cols;
a_arr = a->arr;
b_arr = b->arr;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
if (a_arr[i][j] != b_arr[i][j])
{
return res;
}
}
}
res = 1;
return res;
}
拷贝图像:
static void uint8_2d_copy(uint8_2d* src, uint8_2d* dst)
{
if (!src || !dst)return;
uint8_t** src_arr;
uint8_t** dst_arr;
int rows, cols;
int i;
assert(src->cols == dst->cols && src->rows == dst->rows);
rows = src->rows;
cols = src->cols;
src_arr = src->arr;
dst_arr = dst->arr;
for (i = 0; i < rows; i++)
{
memcpy(dst_arr[i], src_arr[i], cols);
}
}
两幅图像相与:
static void uint8_2d_and(uint8_2d* a, uint8_2d* b, uint8_2d* res)
{
if (!a || !b || !res)return;
int rows, cols;
int i, j;
uint8_t** a_arr;
uint8_t** b_arr;
uint8_t** res_arr;
assert(a->cols == b->cols && a->rows == b->rows);
rows = a->rows;
cols = b->cols;
a_arr = a->arr;
b_arr = b->arr;
res_arr = res->arr;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
res_arr[i][j] = a_arr[i][j] & ~b_arr[i][j];
}
}
}
1/4尺寸图像拷贝,拷贝的是奇数行或偶数行,需要指定开始的行号或列号,隔行隔列拷贝
static void uint8_2d_copy_quarter_data(uint8_2d* src, uint8_2d* dst, int rs, int cs)
{
if (!src || !dst)return;
uint8_t** src_arr;
uint8_t** dst_arr;
int rows, cols;
int i, j;
assert(src->cols == dst->cols && src->rows == dst->rows);
rows = src->rows;
cols = src->cols;
src_arr = src->arr;
dst_arr = dst->arr;
for (i = rs; i < rows; i+=2)
{
for (j = cs; j < cols; j+=2)
{
dst_arr[i][j] = src_arr[i][j];
}
}
查找表处理,图像的某点8邻域,权重值:
8 5 2
7 4 1
6 3 0
这里需要加上当前像素,考虑的是当前像素点加上8邻域的8个点,一个9个点的加权和,得到一个小于等于511的数,所以查找表由512个数构成,这样做的好处是可以节省比较的次数,将3x3的模板匹配换成了比较数字,提升算法执行效率。
static void shrink_look_up(uint8_2d* img, uint8_2d* res)
{
if (!img || !res)return;
int rows, cols;
int i, j;
int index;
uint8_t** arr;
uint8_t** res_arr;
rows = img->rows;
cols = img->cols;
rows--;
cols--;
assert(rows > 1 && cols > 1);
arr = img->arr;
res_arr = res->arr;
for (i = 1; i < rows; i++)
{
for (j = 1; j < cols; j++)
{
index = (arr[i - 1][j - 1] << 8) + (arr[i][j - 1] << 7) +\
(arr[i + 1][j - 1] << 6) + (arr[i - 1][j] << 5) + \
(arr[i][j] << 4) + (arr[i + 1][j] << 3) + \
(arr[i - 1][j + 1] << 2) + (arr[i][j + 1] << 1) + \
arr[i + 1][j + 1];
assert(index < 512);
res_arr[i][j] = shrink_table[index];
}
}
}
整合前面的方法:
void shrink(uint8_2d* img, int n)
{
int iter = 0;
int done = 0;
uint8_2d* m = NULL;
uint8_2d* sub = NULL;
uint8_2d* last_img = NULL;
if (!img)return;
int rows, cols;
rows = img->rows;
cols = img->cols;
m = create_uint8_2d(rows, cols);
sub = create_uint8_2d(rows, cols);
last_img = create_uint8_2d(rows, cols);
if (!m || !sub)
{
destroy_uint8_2d(&m);
destroy_uint8_2d(&sub);
destroy_uint8_2d(&last_img);
return;
}
while (!done)
{
// 一次shrink操作,4次查找表处理
uint8_2d_copy(img, last_img);
shrink_look_up(img, m);
//printf_uint8_2d(m);
uint8_2d_and(img, m, sub);
//printf_uint8_2d(sub);
uint8_2d_copy_quarter_data(sub, img, 0, 0);
//printf_uint8_2d(img);
shrink_look_up(img, m);
uint8_2d_and(img, m, sub);
uint8_2d_copy_quarter_data(sub, img, 1, 1);
shrink_look_up(img, m);
uint8_2d_and(img, m, sub);
uint8_2d_copy_quarter_data(sub, img, 0, 1);
shrink_look_up(img, m);
uint8_2d_and(img, m, sub);
uint8_2d_copy_quarter_data(sub, img, 1, 0);
//printf_uint8_2d(img);
//达到设定次数或者当前二值图与上一次shrink操作结果相同,结束shrink操作
if (iter++ >= n || uint8_2d_equal(last_img, img))
done = 1;
}
}
测试:
void main()
{
#define ROWS 8
#define COLS 8
uint8_t data[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
uint8_2d* img = uint8_2d_create_from_data(&data[0], ROWS, COLS);
if (!img)return;
shrink(img, INT_MAX);
printf_uint8_2d(img);
}