一种测量图像中弯管长度的特征检测技术 [英] Feature detection technique to measure the length of curved tubes in an image

查看:132
本文介绍了一种测量图像中弯管长度的特征检测技术的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有数百张来自荧光显微镜实验的DNA纳米管图像,我想用图像处理以自动方式测量管长度的分布。这是一个示例显微镜图像:

I have hundreds of images of DNA nanotubes from fluorescence microscopy experiments and I would like to measure the distribution of tube lengths in an automated way using image processing. Here is an example microscope image:

我尝试了一些使用python和skimage的特征提取方法。我尝试过使用Canny边缘检测,它成功地创建了每个纳米管的轮廓,但是我不清楚如何从这些轮廓到最终的长度测量。应用Canny边缘检测后,我尝试使用概率Hough变换将直线拟合到曲线,这将使长度测量变得简单。正如你在这些结果中看到的那样:

I have tried a few feature extraction methods using python and skimage. I have tried using Canny edge detection, which successfully creates an outline of each nanotube, however it is unclear to me how to go from these outlines to a definitive measure of length. After applying Canny edge detection I have tried using a probabilistic Hough transform to fit straight lines to the curves, which would make length measurement straightforward. As you can see in these results though:

线条拟合不一致,并且为同一个管道结构并行创建多条线条。

line fitting is inconsistent and multiple lines are created in parallel for the same tube structure.

有谁知道测量这些管长的直接方法?

Does anyone know of a straightforward method to measure these tube lengths?

推荐答案

我会这样开始:


  1. 二值化图片

  2. 查找管的每个设置像素

  3. 通过管颜色填充该位置

使用任意填充算法和8个邻居并在填充期间还计算某些计数器中重新着色的像素 cnt

use any filling algorithm with 8-neighbors and during filling also count recolored pixels in some counter cnt.

如果面积 cnt 太小则将其重新调整为背景否则将其大小 cnt / average_tube_width 计入直方图。

if area size cnt too small recolor it to background otherwise account its size cnt/average_tube_width to the histogram.

这里简单的 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;

输入图片的结果是:

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

The yellow lines are the lengths distribution (x axis is tube length and y is probability)

我使用自己的图片类来处理图像,所以有些成员是:


xs,ys 是图片大小,以像素为单位
< br> p [y] [x] .dd 的像素(x,y)位置为32位整数键入

清除(颜色)使用颜色清除整个图像

调整大小(xs,ys)将图片大小调整为新分辨率

bmp VCL 封装 GDI 位图 Canvas 访问

pf 保持图像的实际像素格式:


xs,ys is size of image in pixels
p[y][x].dd is pixel at (x,y) position as 32 bit integer type
clear(color) clears entire image with color
resize(xs,ys) resizes image to new resolution
bmp is VCL encapsulated GDI Bitmap with Canvas access
pf holds actual pixel format of the image:

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 and pixels are encoded like this:

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,
    };

我还使用我的动态列表模板,所以:

I also use mine dynamic list template so:

,点击列表与LT;双> xxx; double xxx []相同;

xxx.add(5) ; 5 添加到列表的末尾

xxx [7] 访问数组元素(安全)

xxx.dat [7] 访问数组元素(不安全但快速直接访问)

xxx.num 是数组的实际使用大小

xxx.reset()清除数组并设置 xxx.num = 0

xxx.allocate(100) preallocate空间 100 项目


List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items

现在A *填充的实现如下:

Now the A* filling is implemented like this:

// 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个端点,您可以尝试区分管。

If you want to improve your counting based on the size then if you got a multiple of avg size you got intersected tubes. So you can either try to compute how many of them are there and account the avg size to the histogram instead of using the full size or us A* filling and locate the endpoints. If you find more than 2 endpoints you can try to distinct between tubes.

因此,首先使用A * fill查找本地最大值,然后再从该位置再次填充A *(这样您就可以从端点开始填充)。然后找到所有局部最大值,并根据平均尺寸和管的实际尺寸以及端点数量,您可以确定将多少个管组合在一起以及多少个管相互连接。然后你可以尝试在端点之间进行所有可能的组合,每个管最接近平均管尺寸的那个是最正确的。这样可以提高精确度。

So first use A* fill to find local max and then A* fill again from that position (so you start filling from endpoint). Then find all local maxs and based on avg size and actual size of tube and number of endpoints you can determine how many tubes are grouped together and how many of them are interconnected. Then you can try to do all possible combinations between endpoints and the one closest to avg size of tube per each tube is the most "correct" one. That should enhance precision a lot more.

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

In case you do not know the avg tube thickness you can use A* filling for non intersecting tubes directly to acquire the length. So in the second fill (from endpoint) when the filling stops the last filled ID is the length of tube in pixels.

这篇关于一种测量图像中弯管长度的特征检测技术的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆