你几乎明白了,但我的想法(来自 cmets)更像是这样的:
-
准备图片
切换到灰度、去除噪点(通过一些模糊)、增强动态范围等。
-
通过两个x,y 轴导出图像并创建渐变二维场
所以重新着色图像并创建 2D 矢量场。每个像素都有RGB,所以一个轴使用R,另一个轴使用B。我这样做:
Blue(x,y)=abs(Intensity(x,y)-Intensity(x-1,y))
Red (x,y)=abs(Intensity(x,y)-Intensity(x,y-1))
子结果如下所示:
-
阈值图像强调边缘
因此选择每个像素并比较 Blue(x,y)+Red(x,y)<treshold 如果为真则重新着色为 unknown 否则重新着色为 edge 颜色。对于您的示例图像,我使用了阈值24 之后平滑结果以用模糊的颜色填充小间隙。子结果如下所示:
绿色是我的未知颜色,白色是边缘。如您所见,我模糊了很多(懒得实现连接的组件)。
-
检测背景
所以现在为了区分背景和里面的骨头,我使用了特殊的填充方法(但一个简单的填充就可以了)我为 DIP 开发的东西,发现它非常有用,比以前的预期好很多倍。
void growfill(DWORD c0,DWORD c1,DWORD c2); // grow/flood fill c0 neigbouring c1 with c2
它只是检查图像中的所有像素,如果在c1 附近找到颜色c0,然后将其重新着色为c2 并循环直到没有发生重新着色。由于不需要递归或堆栈/堆/列表,更大的分辨率通常比洪水填充快得多。它还可以用于许多很酷的效果,如变薄/加厚等,只需几个简单的调用即可。
好的,回到主题我选择3种基色:
//RRGGBB
const DWORD col_unknown =0x00408020; // yet undetermined pixels
const DWORD col_background=0x00000000;
const DWORD col_edge =0x00FFFFFF;
现在背景肯定是在边缘周围,所以我用 col_background 在图像周围绘制矩形,并用col_background 填充col_background 附近的所有col_unknown 像素,这基本上从外到内填充图像。
在此之后,我将所有不是 3 种定义颜色中的任何一种的像素重新着色为最接近的匹配。这将消除模糊,因为它不再是可取的。子结果如下所示:
-
分割/标签
现在只需扫描整个图像,如果发现任何col_unknown 增长,则用对象不同的颜色/索引填充它。更改实际对象的不同颜色/索引(增量)并继续直到图像结束。请注意您必须避免使用 3 种预定颜色的颜色,否则您会合并您不想要的区域。
最终结果如下所示:
-
现在您可以应用任何形式的分析/比较
您获得了每个对象区域的像素掩码,因此您可以计算像素(区域)并删除忽略太小的区域。计算每个对象的平均像素位置(中心)并使用它来检测它实际上是哪个骨骼。计算区域的同质性......重新缩放到模板骨骼......等等......
这是我使用的一些 C++ 代码
color c,d;
int x,y,i,i0,i1;
int tr0=Form1->sb_treshold0->Position; // =24 treshold from scrollbar
//RRGGBB
const DWORD col_unknown =0x00408020; // yet undetermined pixels
const DWORD col_background=0x00000000;
const DWORD col_edge =0x00FFFFFF;
// [prepare image]
pic1=pic0; // copy input image pic0 to output pic1
pic1.pixel_format(_pf_u); // convert to grayscale intensity <0,765>
pic1.enhance_range(); // recompute colors so they cover full dynamic range
pic1.smooth(1); // blur a bit to remove noise
// extract edges
pic1.deriveaxy(); // compute derivations (change in intensity in x and y axis as 2D gradient vector)
pic1.save("out0.png");
pic1.pf=_pf_rgba; // from now on the recolored image will be RGBA (no need for conversion)
for (y=0;y<pic1.ys;y++) // treshold recolor
for (x=0;x<pic1.xs;x++)
{
c=pic1.p[y][x];
i=c.dw[picture::_x]+c.dw[picture::_y]; // i=|dcolor/dx| + |dcolor/dy|
if (i<tr0) c.dd=col_unknown; else c.dd=col_edge; // treshold test&recolor
pic1.p[y][x]=c;
}
pic1.smooth(5); // blur a bit to fill the small gaps
pic1.save("out1.png");
// [background]
// render backround color rectangle around image
pic1.bmp->Canvas->Pen->Color=rgb2bgr(col_background);
pic1.bmp->Canvas->Brush->Style=bsClear;
pic1.bmp->Canvas->Rectangle(0,0,pic1.xs,pic1.ys);
pic1.bmp->Canvas->Brush->Style=bsSolid;
// growth fill all col_unknonw pixels near col_background pixels with col_background similar to floodfill but without recursion and more usable.
pic1.growfill(col_unknown,col_background,col_background);
// recolor blured colors back to their closest match
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
{
c=pic1.p[y][x];
d.dd=col_edge ; i=abs(c.db[0]-d.db[0])+abs(c.db[1]-d.db[1])+abs(c.db[2]-d.db[2]); i0=i; i1=col_edge;
d.dd=col_unknown ; i=abs(c.db[0]-d.db[0])+abs(c.db[1]-d.db[1])+abs(c.db[2]-d.db[2]); if (i0>i) { i0=i; i1=d.dd; }
d.dd=col_background; i=abs(c.db[0]-d.db[0])+abs(c.db[1]-d.db[1])+abs(c.db[2]-d.db[2]); if (i0>i) { i0=i; i1=d.dd; }
pic1.p[y][x].dd=i1;
}
pic1.save("out2.png");
// [segmentation/labeling]
i=0x00202020; // labeling color/idx
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
if (pic1.p[y][x].dd==col_unknown)
{
pic1.p[y][x].dd=i;
pic1.growfill(col_unknown,i,i);
i+=0x00050340;
}
pic1.save("out3.png");
我将自己的图片类用于图像,所以一些成员是:
-
xs,ys 图像大小(以像素为单位)
-
p[y][x].dd 是 (x,y) 位置的像素,为 32 位整数类型
-
p[y][x].dw[2] 是(x,y) 位置的像素,为 2D 字段的 2x16 位整数类型
-
p[y][x].db[4] 是 (x,y) 位置的像素,为 4x8 位整数类型,便于通道访问
-
clear(color) - 清除整个图像
-
resize(xs,ys) - 将图像调整为新分辨率
-
bmp - VCL 封装了 GDI 带有 Canvas 访问的位图
-
smooth(n) - 快速模糊图像n 次
-
growfill(DWORD c0,DWORD c1,DWORD c2) - 生长/洪水填充c0 neigbouring c1 与c2
[Edit1] 基于扫描线的骨骼检测
在链接的 find horizon QA 中,您必须投射扫描线并搜索识别骨骼的不同特征。我将从像这样的图像(在 x 轴上)的部分推导开始::
左边是x的颜色强度推导(灰色表示零),右边是原始图像。侧图是作为x 和y 的函数的推导图,用于实际鼠标位置的行和行。正如您所看到的,每个骨骼在推导中都有不同的形状,可以检测到。我使用了一个非常简单的检测器,如下所示:
- 对于已处理的图像行,通过
x 进行部分推导
- 找到所有峰(圆圈)
- 移除太小的峰并将相同符号的峰合并在一起
- 通过 4 个后续峰检测骨骼:
- 大负面
- 小正
- 小负数
- 大正面
对于每个找到的骨骼边缘,我在原始图像中渲染红色和蓝色像素(在大峰的位置),以直观地检查正确性。您也可以在 y 轴上以相同的方式执行此操作并合并结果。为了改善这一点,您应该使用更好的检测,例如通过使用相关性...
您可以轻松地创建骨骼蒙版,然后将其分割为单独的骨骼并按照上述文本进行处理,而不是边缘渲染。您还可以使用形态学运算来填补任何空白。
我能想到的最后一件事是为骨骼的关节侧添加一些检测(形状不同)。它需要大量的试验,但至少你知道该走哪条路。