我正在研究 CT CT 肺部图像。为了检测结节,我需要为每个疑似结节拟合椭球模型,我该如何制作一个代码??? 结节是可疑的肿瘤对象,我的算法需要检查每个对象,并将其近似为椭圆体,并从椭球参数中我们计算8个特征来构建分类器,通过训练和测试来检测它是否为结节数据,所以我需要适合这样的椭球
这里有一片体积 CT肺部图像
这里是同一个体积的另一个切片,但它包含一个结节(黄色圆圈有一个结节)所以我需要我的代码来检查每个形状以确定它是否是一个结节
答案 0 :(得分:3)
由于我们没有 3D 数据集,我从 2D 开始。
所以首先我们需要选择肺部,这样我们就不会计算任何其他物体了。由于这是灰度,我们首先需要以某种方式将其二值化。我使用自己的class picture
进行 DIP ,这将大量使用我的growthfill
,因此我强烈建议您先阅读此内容:
您将在这里找到所需的所有解释。现在为了你的任务我会:
将RGBA转换为灰度<0,765>
我只计算强度i=R+G+B
和24位图像一样,通道为8位,结果最多为3*255 = 765
。由于输入图像是由JPEG压缩的,因此图像中的颜色和噪声都会出现失真,所以不要忘记这一点。
裁剪出白色边框
从边界的每个外线的中间向中间投射光线(扫描线),如果非白色像素点击则停止。我使用700
而不是765
来设置阈值以补偿图像中的噪点。现在你得到了可用图像的边界框,以便裁掉剩下的部分。
计算直方图
为了补偿图像中的失真,直方图足以消除所有不需要的噪声和间隙。然后从左侧和右侧找到局部最大值(红色)。这将用于二值化阈值(它们之间的中间绿色)这是我的最终结果:
二值图像
仅针对直方图中的**绿色*强度的阈值图像。因此,如果i0,i1
是直方图中左右两侧的局部最大强度,那么对(i0+i1)/2
的阈值。这是结果:
删除除肺以外的所有内容
这很简单,只需将外部的黑色填充到某些预定义的背景颜色即可。然后以相同的方式将所有白色东西相邻的背景颜色。这将消除人体表面,骨骼,器官和 CT 机器,只留下肺部。现在使用一些预定义的肺部颜色重新着色剩余的黑色。
不应留下黑色,剩下的白色是可能的结节。
处理所有剩余的白色像素
因此,只需循环浏览图像,并在第一个白色像素点击洪水中填充预定义的结节颜色或不同的对象索引供后者使用。我还区分了表面(浅绿色)和内部(洋红色)。这是结果:
现在您可以计算每个结节的功能。如果您为此编写自定义floodfill
代码,则可以直接从中获取以下内容:
[pixels]
[pixels]
所有这些都可以用作你的特征变量,也可以用来帮助拟合。
适合找到的表面点
有很多方法可以解决这个问题,但我会尽可能地提高性能和准确性。例如,您可以使用质心作为椭圆体中心。然后从中找到min
和max
个远点,并将它们用作半轴(+/-某些正交校正)。然后只搜索这些初始值。有关详细信息,请参阅:
您可以在相关的 Q / A 中找到使用示例。
<强> [注释] 强>
所有项目符号都适用于 3D 。构造自定义floodfill
时要小心递归尾部。太多的信息真的会快速溢出你的堆栈并且还会大大减慢速度。这里有一个小例子,说明我如何使用我使用的自定义返回参数+ growthfill
来处理它:
//---------------------------------------------------------------------------
void growfill(DWORD c0,DWORD c1,DWORD c2); // grow/flood fill c0 neigbouring c1 with c2
void floodfill(int x,int y,DWORD c); // flood fill from (x,y) with color c
DWORD _floodfill_c0,_floodfill_c1; // recursion filled color and fill color
int _floodfill_x0,_floodfill_x1,_floodfill_n; // recursion bounding box and filled pixel count
int _floodfill_y0,_floodfill_y1;
void _floodfill(int x,int y); // recursion for floodfill
//---------------------------------------------------------------------------
void picture::growfill(DWORD c0,DWORD c1,DWORD c2)
{
int x,y,e;
for (e=1;e;)
for (e=0,y=1;y<ys-1;y++)
for ( x=1;x<xs-1;x++)
if (p[y][x].dd==c0)
if ((p[y-1][x].dd==c1)
||(p[y+1][x].dd==c1)
||(p[y][x-1].dd==c1)
||(p[y][x+1].dd==c1)) { e=1; p[y][x].dd=c2; }
}
//---------------------------------------------------------------------------
void picture::_floodfill(int x,int y)
{
if (p[y][x].dd!=_floodfill_c0) return;
p[y][x].dd=_floodfill_c1;
_floodfill_n++;
if (_floodfill_x0>x) _floodfill_x0=x;
if (_floodfill_y0>y) _floodfill_y0=y;
if (_floodfill_x1<x) _floodfill_x1=x;
if (_floodfill_y1<y) _floodfill_y1=y;
if (x> 0) _floodfill(x-1,y);
if (x<xs-1) _floodfill(x+1,y);
if (y> 0) _floodfill(x,y-1);
if (y<ys-1) _floodfill(x,y+1);
}
void picture::floodfill(int x,int y,DWORD c)
{
if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;
_floodfill_c0=p[y][x].dd;
_floodfill_c1=c;
_floodfill_n=0;
_floodfill_x0=x;
_floodfill_y0=y;
_floodfill_x1=x;
_floodfill_y1=y;
_floodfill(x,y);
}
//---------------------------------------------------------------------------
这里的 C ++ 代码我用以下代码执行了示例图片
picture pic0,pic1;
// pic0 - source img
// pic1 - output img
int x0,y0,x1,y1,x,y,i,hist[766];
color c;
// copy source image to output
pic1=pic0;
pic1.pixel_format(_pf_u); // grayscale <0,765>
// 0xAARRGGBB
const DWORD col_backg=0x00202020; // gray
const DWORD col_lungs=0x00000040; // blue
const DWORD col_out =0x0000FFFF; // aqua nodule surface
const DWORD col_in =0x00800080; // magenta nodule inside
const DWORD col_test =0x00008040; // green-ish distinct color just for safe recoloring
// [remove white background]
// find white background area (by casting rays from middle of border into center of image till non white pixel hit)
for (x0=0 ,y=pic1.ys>>1;x0<pic1.xs;x0++) if (pic1.p[y][x0].dd<700) break;
for (x1=pic1.xs-1,y=pic1.ys>>1;x1> 0;x1--) if (pic1.p[y][x1].dd<700) break;
for (y0=0 ,x=pic1.xs>>1;y0<pic1.ys;y0++) if (pic1.p[y0][x].dd<700) break;
for (y1=pic1.ys-1,x=pic1.xs>>1;y1> 0;y1--) if (pic1.p[y1][x].dd<700) break;
// crop it away
pic1.bmp->Canvas->Draw(-x0,-y0,pic1.bmp);
pic1.resize(x1-x0+1,y1-y0+1);
// [prepare data]
// raw histogram
for (i=0;i<766;i++) hist[i]=0;
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
hist[pic1.p[y][x].dd]++;
// smooth histogram a lot (remove noise and fill gaps due to compression and scanning nature of the image)
for (x=0;x<100;x++)
{
for (i=0;i<765;i++) hist[i]=(hist[i]+hist[i+1])>>1;
for (i=765;i>0;i--) hist[i]=(hist[i]+hist[i-1])>>1;
}
// find peaks in histogram (for tresholding)
for (x=0,x0=x,y0=hist[x];x<766;x++)
{
y=hist[x];
if (y0<y) { x0=x; y0=y; }
if (y<y0>>1) break;
}
for (x=765,x1=x,y1=hist[x];x>=0;x--)
{
y=hist[x];
if (y1<y) { x1=x; y1=y; }
if (y<y1>>1) break;
}
// binarize image (tresholding)
i=(x0+x1)>>1; // treshold with middle intensity between peeks
pic1.pf=_pf_rgba; // result will be RGBA
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
if (pic1.p[y][x].dd>=i) pic1.p[y][x].dd=0x00FFFFFF;
else pic1.p[y][x].dd=0x00000000;
pic1.save("out0.png");
// recolor outer background
for (x=0;x<pic1.xs;x++) pic1.p[ 0][x].dd=col_backg; // render rectangle along outer border so the filling starts from there
for (x=0;x<pic1.xs;x++) pic1.p[pic1.ys-1][x].dd=col_backg;
for (y=0;y<pic1.ys;y++) pic1.p[y][ 0].dd=col_backg;
for (y=0;y<pic1.ys;y++) pic1.p[y][pic1.xs-1].dd=col_backg;
pic1.growfill(0x00000000,col_backg,col_backg); // fill its black content outside in
// recolor white human surface and CT machine
pic1.growfill(0x00FFFFFF,col_backg,col_backg);
// recolor Lungs dark matter
pic1.growfill(0x00000000,col_backg,col_lungs); // outer border
pic1.growfill(0x00000000,col_lungs,col_lungs); // fill its black content outside in
pic1.save("out1.png");
// find/recolor individual nodules
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
if (pic1.p[y][x].dd==0x00FFFFFF)
{
pic1.floodfill(x,y,col_test);
pic1.growfill(col_lungs,col_test,col_out);
pic1.floodfill(x,y,col_in);
}
pic1.save("out2.png");
// render histogram
for (x=0;(x<766)&&(x>>1<pic1.xs);x++)
for (y=0;(y<=hist[x]>>6)&&(y<pic1.ys);y++)
pic1.p[pic1.ys-1-y][x>>1].dd=0x000040FF;
for (x=x0 ,y=0;(y<=100)&&(y<pic1.ys);y++) pic1.p[pic1.ys-1-y][x>>1].dd=0x00FF0000;
for (x=x1 ,y=0;(y<=100)&&(y<pic1.ys);y++) pic1.p[pic1.ys-1-y][x>>1].dd=0x00FF0000;
for (x=(x0+x1)>>1,y=0;(y<=100)&&(y<pic1.ys);y++) pic1.p[pic1.ys-1-y][x>>1].dd=0x0000FF00;
答案 1 :(得分:1)
您可能对我们为开源软件Icy http://icy.bioimageanalysis.org/
开发的最新插件感兴趣插件名称为FitEllipsoid,它允许通过首先单击正交视图上的几个点来快速将椭球拟合到图像内容。 这里有一个教程:https://www.youtube.com/watch?v=MjotgTZi6RQ
另请注意,我们在GitHub上提供了Matlab和Java源代码(但我不能提供它们,因为它是我第一次出现在网站上)。