細線化

 今更細線化もないだろうとは思うのですが,必要になったので実装してみた.参考にしたのはOpenCV/細線化なんですが,一応これの参考文献にもあたっています.

 アルゴリズムは同じですが,実装はちょっと工夫してみた.OpenCVだから速いかというと全然そんなことはなくて,自分の実装の法が100倍ぐらい速くなりました.もちろん画像の中身依存なんですけどね.肝は,画像全体から削減可能な画素を探すのではなくて,ランレングスで黒画素のみを保持しておいて検査するところ.

 というわけで,コアの部分だけ晒しておきます*1.半分ぐらいはランレングスの管理が占めていますね.今の設定では8回に1回ランレングスを再構築しています(cnt%8==0の部分).

 今時Cでゴリゴリ画像処理を書いている人もいないのかなぁ.というか,基本ライブラリにOpenCVを使っていないことの方が問題か.

struct runlen_s
{
    int sx, ex, y, ln;
    struct runlen_s *next;
};
typedef struct runlen_s runlen_t;

runlen_t *new_runlen(image_t *img)
{
    runlen_t *rl;
    int ri;
    rl = malloc(sizeof(runlen_t)*(px_ys(img)+1));
    for (ri = 0; ri < px_ys(img); ri++) {
        rl[ri].sx = 0;
        rl[ri].ex = px_xs(img);
        rl[ri].y = ri;
    }
    rl[ri].sx = -1;
    return rl;
}

runlen_t *get_runlen(image_t *img, runlen_t *rl_)
{
    runlen_t *rl = NULL;
    int ri, ri_, rn, x, y, onfg;
    if ((!img) || (!rl_)) { error("get_runlen: img/rl is %X/%X.", img, rl); }
    for (ri = ri_ = rn = 0; 0 <= rl_[ri_].sx; ri_++) {
        for (onfg = 0, y = rl_[ri_].y, x = rl_[ri_].sx; x < rl_[ri_].ex; x++) {
            if (onfg == 0 && px_(img,x,y) == 1) {
                if (rn <= ri) {
                    rn += 1000;
                    rl = (runlen_t*)realloc(rl, sizeof(runlen_t)*rn);
                    if (!rl) { error("runlen realloc error : %d.", rn); }
                }
                rl[ri].sx = x; rl[ri].y = y;
                onfg = 1;
            }
            else if (onfg != 0 && px_(img,x,y) != 1) {
                rl[ri++].ex = x;
                onfg = 0;
            }
        }
        if (onfg != 0) {
            rl[ri++].ex = x;
        }
    }
    rl[ri].sx = -1;
    return rl;
}

int get_runlen_num(runlen_t *rl)
{
    int ri;
    if (!rl) { error("get_runlen_num: rl is NULL."); }
    for (ri = 0; 0 <= rl[ri].sx; ri++) ; // do nothing
    return ri;
}

image_t *thinning(image_t *img)
{
    image_t *mask;
    int fg, bg, x, y, sum, ker, cnt;
    int ri, rn;
    runlen_t *rl, *rl_;
    mask = image_filled2(img, 0);
    int xo[] = {-1, 0,-1,+1, 0, -1, 0,+1,-1, 0,  0,+1,+1,-1, 0, +1,+1,+1,-1,-1,
                 0,+1,+1,-1, 0, -1,+1, 0,+1, 0, -1,-1, 0,+1, 0, -1,-1,-1,+1,+1};               
    int yo[] = {-1,-1, 0, 0,+1, -1,-1,-1,+1,+1, -1,-1, 0, 0,+1, -1, 0,+1,-1, 0,
                +1,+1, 0, 0,-1, +1,+1,+1,-1,-1,  0,+1,+1, 0,-1, -1, 0,+1, 0,+1};
    int x0, x1, x2, x3, x4, y0, y1, y2, y3, y4;

    // binarization foreground:1 background:0
    img = image_dup(img); image_forall(img,x,y) { px(img,x,y) = (px(img,x,y)<128 ? 1 : 0); }

    rl = new_runlen(img);
    ker = sum = cnt = 0;
    while(1) {
        if ((ker==0) && (cnt%8==0)) {
            rl_ = get_runlen(img, rl); free(rl); rl = rl_;
            rn = get_runlen_num(rl);
        }
        x0=xo[ker*5+0]; x1=xo[ker*5+1]; x2=xo[ker*5+2]; x3=xo[ker*5+3]; x4=xo[ker*5+4];
        y0=yo[ker*5+0]; y1=yo[ker*5+1]; y2=yo[ker*5+2]; y3=yo[ker*5+3]; y4=yo[ker*5+4];
        for (ri = 0; ri < rn; ri++) for (y = rl[ri].y, x = rl[ri].sx; x < rl[ri].ex; x++) {
            if (px_(img,x,y) == 0) { continue; }
            fg = (px0(img,x+x0,y+y0)+px0(img,x+x1,y+y1)+px0(img,x+x2,y+y2) == 0);
            bg = (px0(img,x+x3,y+y3)+px0(img,x+x4,y+y4) == 2);
            if (fg && bg) { px_(mask,x,y) = 1; sum++; }
        }
        for (ri = 0; ri < rn; ri++) for (y = rl[ri].y, x = rl[ri].sx; x < rl[ri].ex; x++) {
            if (px_(mask,x,y)) {
                px_(img,x,y) = px_(mask,x,y) = 0;
            }
        }
        ker = (ker+1)%8;
        if (ker == 0) {
            if (sum) { sum = 0; cnt++; }
            else     { break; }
        }
    } 
    image_free(mask);
    image_forall(img,x,y) { px(img,x,y) = px(img,x,y) ? 0 : 255; }
    return img;
}

*1:自作ライブラリは該当部分を持ってくるのが面倒