2017-06-21 88 views
3

我从荧光显微镜实验中获得了数百个DNA纳米管图像,并且我想使用图像处理以自动方式测量管长度的分布。下面是一个例子的显微镜图像:用于测量图像中弯曲管长度的特征检测技术

example DNA nanotube image

我曾尝试使用Python和skimage几个特征提取方法。我尝试过使用Canny边缘检测,它成功地创建了每个纳米管的轮廓,但是我不清楚如何从这些轮廓去确定长度。应用Canny边缘检测后,我尝试使用概率Hough变换来拟合曲线的直线,这将使得长度测量变得简单。正如可以在这些结果虽然看到:

Canny edge detection and hough transform results

线拟合是不一致的和多行中对于相同的管结构平行创建。

有没有人知道一个简单的方法来测量这些管长度?

+1

这是在我的驾驶室外面的方式,但它好像你想找到由Canny边缘检测创建的​​形状的中心线。这是OpenCV的一个相关问题。也许那里的链接会给你一些想法? https://stackoverflow.com/questions/21039535/opencv-extract-path-centerline-from-arbitrary-area –

+1

我尝试了Canny边缘检测,然后通过形态学关闭来填补空白,然后进行镂空以找到中心线每种形状。我用红色覆盖了原来的东西。看看你对结果的看法... http://thesetchells.com/StackOverflow/nano.png –

+0

谢谢你们俩!骷髅绝对看起来像要走的路。 @MarkSetchell这看起来正是我想要的!有些管子的主干线上有小分支,但应该很容易通过基于线路长度的过滤来去除。是否有一种简单的方法可以访问每条线的起点和终点以及总长度? – mpacella

回答

2

我应该像这样开头:

  1. 值化图像
  2. 找到管的每一组像素
  3. 洪水填补这个位置由管颜色

    使用任何填充算法与8邻居和在填充期间也计算重新着色的像素在一些柜台cnt

    如果面积大小cnt太小,将其重新着色至背景,否则将其大小cnt/average_tube_width记录到直方图。的这个

这里简单C++例如:

picture pic0,pic1; 
    // pic0 - source img 
    // pic1 - output img 
//     0xAARRGGBB 
const DWORD col_backg=0x00202020; // gray 
const DWORD col_tube =0x00FFFFFF; // white 
const DWORD col_done =0x0000A0FF; // aqua 
const DWORD col_noise=0x00000080; // blue 
const DWORD col_error=0x00FF0000; // red (too smal _hist value) 
const DWORD col_hist =0x00FFFF00; // yellow 
const DWORD col_test =0x01000000; // A* filling start color (must be bigger then all other colors used) 
int x,y,xx,yy,i; 
DWORD c; 
const int _hist=256; // max area size for histogram 
int hist[_hist];  // histogram 

// copy source image to output 
pic1=pic0; 
pic1.enhance_range();    // maximize dynamic range <0,255>^3 
pic1.pixel_format(_pf_u);   // convert to grayscale <0,765> 
pic1.threshold(100,766,col_backg,col_tube); // threshold intensity to binarize image 
pic1.pf=_pf_rgba;     // set as RGBA (without conversion) 

// clear histogram 
for (i=0;i<_hist;i++) hist[i]=0; 
// find all tubes 
for (y=0;y<pic1.ys;y++) 
for (x=0;x<pic1.xs;x++) 
    if (pic1.p[y][x].dd==col_tube) 
    { 
    pic1.Astarfill(x,y,col_test); // fill count area (8 neighbors) 
    if (pic1._floodfill_n>5)  // if valid size 
     { 
     c=col_done;     // set recolor color to done 
     // update histogram 
     if (pic1._floodfill_n<_hist) hist[pic1._floodfill_n]++; 
     else c=col_error; 
     } 
    else c=col_noise; 
    // recolor filled bbox with c 
    for (yy=pic1._floodfill_y0;yy<=pic1._floodfill_y1;yy++) 
    for (xx=pic1._floodfill_x0;xx<=pic1._floodfill_x1;xx++) 
     if (pic1.p[yy][xx].dd>=col_test) 
     pic1.p[yy][xx].dd=c; 
    } 
// render histogram 
for (x=0;x<_hist;x++) 
for (i=0,y=pic1.ys-1;(y>=0)&&(i<hist[x]<<2);y--,i++) 
    pic1.p[y][x].dd=col_hist; 

您的输入的图像的结果是:

result

黄线是长度分布(x轴是管长和y是概率)

使用我自己的图片类的图像,以便一些成员是:


xs,ys是以像素
p[y][x].dd图像的大小是在(x,y)位置作为32位整数类型像素
clear(color)清除整个图像与color
resize(xs,ys)将图像调整为新分辨率
bmpVCL包封GDI位图与Canvas访问
pf保持图像的实际像素格式:

enum _pixel_format_enum 
    { 
    _pf_none=0, // undefined 
    _pf_rgba, // 32 bit RGBA 
    _pf_s,  // 32 bit signed int 
    _pf_u,  // 32 bit unsigned int 
    _pf_ss,  // 2x16 bit signed int 
    _pf_uu,  // 2x16 bit unsigned int 
    _pixel_format_enum_end 
    }; 


color和像素进行编码这样的:

union color 
    { 
    DWORD dd; WORD dw[2]; byte db[4]; 
    int i; short int ii[2]; 
    color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; }; /*color* operator = (const color &a) { ...copy... return this; };*/ 
    }; 


的条带是:

enum{ 
    _x=0, // dw 
    _y=1, 

    _b=0, // db 
    _g=1, 
    _r=2, 
    _a=3, 

    _v=0, // db 
    _s=1, 
    _h=2, 
    }; 

我也用我的动态列表模板,以便:


List<double> xxx;相同double xxx[];
xxx.add(5);增加5结束列表的
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速直接访问)
xxx.num是阵列的实际使用尺寸
xxx.reset()清除阵列并设置xxx.num=0
xxx.allocate(100)100项目

现在A *填充这样实现的预分配的空间:

// these are picture:: members to speed up recursive fillings 
int _floodfill_rn;           // anti stack overflow recursions 
List<int> _floodfill_xy;          // anti stack overflow pendng recursions 
int _floodfill_a0[4];           // recursion filled color and fill color 
color _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; 

// here the filling I used 
void picture::Astarfill(int x,int y,DWORD id) 
    { 
    _floodfill_c0=p[y][x]; 
    _floodfill_c1.dd=id; 
    _floodfill_n=0; 
    _floodfill_x0=x; 
    _floodfill_y0=y; 
    _floodfill_x1=x; 
    _floodfill_y1=y; 
    _floodfill_rn=0; 
    _floodfill_xy.num=0; 

    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return; 

    int i; 
    List<int> xy0,xy1,*p0,*p1,*pp; 
    // first point 
    p0=&xy0; 
    p1=&xy1; 
    p0->num=0; 
    p0->add(x); p0->add(y); p[y][x].dd=id; _floodfill_n++; 
    for (;p0->num;) 
     { 
     p1->num=0; id++; 
     for (i=0;i<p0->num;) 
      { 
      x=p0->dat[i]; i++; 
      y=p0->dat[i]; i++; 
      x--; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      y--; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      x++; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      x++; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      y++; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      y++; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      x--; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      x--; if ((x>=0)&&(y>=0)&&(x<xs)&&(y<ys)&&(p[y][x].dd==_floodfill_c0.dd)){ p1->add(x); p1->add(y); p[y][x].dd=id; _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; } 
      } 
     pp=p0; p0=p1; p1=pp; 
     } 
    _floodfill_rn=id-1; 
    } 

如果您想根据大小来提高你的计数,然后如果你有平均大小的倍数你得到相交的管子。因此,您可以尝试计算其中有多少个,并将平均大小计入直方图,而不是使用完整大小或我们A *填充并找到端点。如果您发现超过2个端点,您可以尝试区分不同的管。

A* filling

所以第一次使用A *从该位置(于是你开始从端点填充)填写找到本地最大值,然后A *填充一次。然后找到所有本地最大值,并根据平均管的尺寸和管的实际尺寸以及端点的数量,确定有多少个管组合在一起以及它们中有多少个相互连接。然后你可以试着做所有可能的端点之间的组合,并且每个管最接近平均尺寸的管是最“正确”的。这应该会提高精度。

如果你不知道平均管厚度,你可以直接使用A *填充非相交管来获得长度。因此,在第二次填充(从端点)填充停止时,最后填充的ID是管的长度(以像素为单位)。