图形宝石IV。使用邻域地图进行二值图像细化

时间:2010-02-14 21:58:36

标签: c++ c graphics image-processing

这个表达式的算法是什么意思?

p = ((p<<1)&0666) | ((q<<3)&0110) | (Image->scanLine(y+1)[x+1] != 0);

“图形宝石IV”一书中的“使用Neigborhood地图进行二值图像细化”算法:

    static int  masks[] = {0200, 0002, 0040, 0010};

 uchar delete_[512] = 
 {
  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,
  0,0,0,1,0,0,1,1,  0,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,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,  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,
  0,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,1,1,1,1,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,1,1,1,1,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,
  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,1,1,1,1,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,1,1,1,1,1,1
 };

  int xsize, ysize; 
  int x, y; 
 int i;  
 int count = 1; 
 int p, q;  
 uchar *qb;
 int m;


 xsize = Image->width();
 ysize = Image->height();
 qb = (uchar*) malloc (xsize*sizeof(uchar));
 qb[xsize-1] = 0;

    while(count) 
 { 
  count = 0;
  for (i = 0; i < 4; ++i) 
  {
   m = masks[i];
   p = Image->scanLine(0)[0] != 0;

   for (x = 0; x < xsize-1; ++x)
    qb[x] = p = ((p<<1)&0006) | (Image->scanLine(0)[x+1] != 0);

   // Scan image for pixel deletion candidates.
   for (y = 0; y < ysize-1; ++y) 
   {
    q = qb[0];
    p = ((q<<3)&0110) | (Image->scanLine(y+1)[0] != 0);

    for (x = 0; x < xsize-1; ++x) 
    { 
     q = qb[x];
     p = ((p<<1)&0666) | ((q<<3)&0110) | (Image->scanLine(y+1)[x+1] != 0);
     qb[x] = p;
     if  ((p&m)==0 && delete_[p])
     {
      count++;
      Image->scanLine(y)[x] = 0; 
     } 
    }

2 个答案:

答案 0 :(得分:4)

(见commented source code

变量mpqqb数组的元素是9位数字,代表像素的3x3像素“邻域”

假设你的图像看起来像这样(每个字母代表一个像素,它是'开'或'关'(1或0,黑色或白色):

    ---x---
    0123456
| 0 abcdefg
| 1 hijklmn
y 2 opqrstu
| 3 vwxyz{|

(x,y)位置(2,1)处的像素是j。该像素的邻域是

bcd
ijk  // 3x3 grid centered on j
pqr

由于每个像素具有二进制值,因此邻域可以用9位表示。上面的邻域可以线性写出,以二进制表示为bcd_ijk_pqr。连续3个像素的分组使得八进制成为一个很好的选择,因为每个八进制数字代表三位。

一旦将邻域表示为9位值,就可以对其进行位操作。诸如((p << 1) & 0666)之类的操作占用邻域,将所有位移到左边的一个位置,并清除最右边的位列。例如,班次将bcd_ijk_pqr更改为cdi_jkp_qr@(其中@表示“空”位,默认为0)。然后掩码将其更改为cd@_jk@_qr@。以3x3网格形式表示:

cd@
jk@
qr@

基本上,整个网格已经向左移动。

类似地,诸如((q << 3) & 0110)的操作将所有位移位三个位置(向上移动行)并清除前两列的位。因此bcd_ijk_pqr变为ijk_pqr_@@@,然后在屏蔽后变为@@k_@@r_@@@

算法的要点是评估每个像素的邻域以确定是否关闭该像素(删除它)。这一行做了评估:

if  ((p&m)==0 && delete_[p])

在该行之前的所有内容都是为了在p中设置邻域。编写代码,以便每次传递每个像素值一次。

qb数组存储上一扫描线中每个像素的邻域。请注意,qb的元素只有8位宽。这意味着省略了邻域的左上角像素。这不是问题,因为任何时候使用qb,它都会向上移动。

所以回答你关于这条线的作用的问题:

p = ((p<<1)&0666) | ((q<<3)&0110) | (Image->scanLine(y+1)[x+1] != 0);

通过合并以下内容创建像素的邻域:

  • 同一行上前一个像素的邻域,向左移动
  • 像素附近的右列高一行,向上移动
  • 图像的(x + 1,y + 1)像素,放入“西南”角落

例如,关于j的邻域计算如下:

p = bc@_ij@_pq@ | @@d_@@k_@@@ | r

    bc@ | @@d | @@@   bcd
p = ij@ | @@k | @@@ = ijk
    pq@ | @@@ | @@r   pqr

答案 1 :(得分:2)

看起来像是一种按位操纵。你不理解个别的操作,或者你不明白为什么正在进行操作(如果后者,算法描述的链接是必不可少的,BTW)?

该行中的

Operators

  • <<右移运算符(根据RH参数将p的位移动到更重要的位置)
  • &在此上下文中为按位和
  • |按位或

这里要记住的一个有用的事情是以“0”开头的整数文字用 octal 表示,这意味着你可以从屏幕上的表示中读取二进制数字(好吧,无论如何,只需要一点点练习。这里的常数是(666)_8 =(110110110)_2和(0110)_8 =(001001000)_2。