用 hough 变换检测抛物线

告诉你某张图中很可能有某个东西长得很像开口向上的抛物线,想把它检测出来,嘿嘿,看上去很难吧?

其实呢,这种事情很适合用 hough 变换 来做:

假设抛物线方程为 y = a ( x - b )2 + c

先把图像边缘检测出来,对于图像边缘上的每一个点 (x,y), 可以有很多条抛物线穿过它,即有很多个 a, b, c 可以使上面的方程成立,于是, 你很暴力的把这些 a, b, c 都记下来! ,对于每个 x,y 点都记下一次,然后看看哪些 a, b, c 的组合被命中的次数比较多就好了。

当然,上面的那个过程有不少地方是可以优化的,比如说对于图片,b 和 c 肯定是整数值,而且 b 很可能在比较偏中心的位置,而 a 的取值范围可以预先设定,比如说如果你想检测下巴,保守的说 a 肯定就是小于 1 的(不然那下巴也太尖了吧!),如此这般等等——而 OpenCV 中用 hough 变换检查圆的代码中显然还有些其他的不那么容易理解的优化,反正我也用不着把它做得很高效,就没有研究了。

下面上图:

(图片均来源于 pixdaus.com ,如果碰巧它侵犯了您的版权,请与我联系)

 

再上代码——

 

(你确定要看吗??!!)

 

 

(警告!魔数出没!)

 

 

 

(警告!没有注释!)

 

 

 

 

(警告!诡异的变量名出没!)

 

 

 

 

 

(警告!你的大脑可能因为看到这种乱七八糟的代码受到永久性的损伤!!)

 

 

 

 

 

 

(你确定依然要看吗??!!!!)

 

 

 

 

 

 

 

// y = a/150*(x-b)^2+c;
struct Parabola {
    int a;
    int b;
    int c;
    Parabola():a(0),b(0),c(0){}
    Parabola(int a, int b, int c):a(a),b(b),c(c){}
    Parabola(Parabola const& p):a(p.a),b(p.b),c(p.c){}
};
struct Record {
    Parabola para;
    int      count;
    Record():para(),count(0){}
    Record(int a, int b, int c, int cnt):para(a,b,c),count(cnt){}
};
struct RecordCmp {
    inline bool operator()(Record const& a, Record const& b) {
        return a.count>b.count;
    }
};
std::vector<Parabola> detectParabolas( cv::Mat const& img,
                                       int minA=-1,
                                       int maxA=-1,
                                       int minB=-1,
                                       int maxB=-1,
                                       int minC=-1,
                                       int maxC=-1 )
{
    cv::Mat edges;
    // cv::imshow("debug", img);
    // cv::waitKey(3000);
    cv::Canny( img, edges, 50, 150 );
    cv::Mat_<uint8_t> img_(edges);
    // cv::imshow("debug", edges);
    // cv::waitKey(3000);
    int ***acc = 0;

    if(minA < 0)
        minA = 1;
    if(minB < 0)
        minB = img.cols/4;
    if(minC < 0)
        minC = img.rows/4;
    if(maxA < 0)
        maxA = 60;
    if(maxB < 0)
        maxB = img.cols/2+img.cols/4;
    if(maxC < 0)
        maxC = img.rows;

    acc = new int**[maxA+1];
    for(int i=0; i<maxA+1; ++i) {
        acc[i] = new int*[maxB+1];
        for(int j=0; j<maxB+1; ++j) {
            acc[i][j] = new int[maxC+1];
            for(int k=minC; k<maxC+1; ++k) {
                acc[i][j][k]=0;
            }
        }
    }

    printf("calculating ...\n");
    int percent=0;
    for(int y=0; y<img.rows; ++y) {
        for(int x=0; x<img.cols; ++x) {
            if(img_(y,x)) {
                for(int a=minA; a<=maxA; ++a){
                    for(int b=minB; b<maxB; ++b) {
                        int c=y+a*(x-b)*(x-b)/150;
                        if(c>=minC && c<=maxC) {
                            ++acc[a][b][c];
                        }
                    }
                }
            }
        }
        int p=int(round(double(y)/img.rows*100.));
        if(p!=percent) {
            percent = p;
            printf("> %02d%%\n", percent);
        }
    }
    printf("done!\n");

    sizedpq<Record, RecordCmp> pq(3);
    for(int i=minA; i<=maxA; ++i) {
        for(int j=minB; j<=maxB; ++j) {
            for(int k=minC; k<=maxC; ++k) {
                pq.insert(Record(-i,j,k, acc[i][j][k]));
            }
        }
    }

    for(int i=0; i<=maxA; ++i) {
        for(int j=0; j<=maxB; ++j) {
            delete[] acc[i][j];
        }
        delete[] acc[i];
    }
    delete[] acc;

    std::vector<Parabola> res;
    for(int i=0; i<3; ++i) {
        res.push_back(pq[i].para);
    }
    return res;
}

上面那段代码最诡异而需要说明的地方是 a ,我希望检测的是开口向上的抛物线,于是在计算机图像的坐标系统下 a 的取值应该是负数,但作为数组下标时它当然得要是正数,所以这里多绕了几个弯,下次我自己看到肯定也会忘记的,不过还好这算法相当简单,下次需要用到我肯定更愿意重写。

sizedpq 类是俺以前做马赛克拼接图像时折腾出来的一个固定大小的优先队列类,其实设计得并不好,不过接口挺好用,所以.. 就拿来用了。

 

PS. 速度与图像边缘数量关系很密切,不过通常情况下处理一张上面展示的那种大小的图像也就需要 3 秒钟左右吧,其实还可以接受。

PPS. 人果然还是逼出来的啊,今早老宋很客气的问我节日快乐,弄得我很不好意思的在两个小时之内把这个做好了……

PPPS. 上周末和室友去西湖边逛了一圈,在柳浪闻莺景区里,我刚拍完一张照,发现旁边飘过了一个长发白衣飘逸出尘的美女背影,我说哇美女,他说其实不是美女我已经观察很久了...... 唉,男人。

PPPPS. 谁能教我玩 DotA 啊!!!

流水帐+意识流

灰机

第二次坐灰机,在这个位置坐着,玻璃如果破了我就会被吸出去,然后在这个引擎里面被卷死掉,引擎内圈会变成红色,我的血和肉块会洒到地面,然而由于是晚上所以地面上不见得会有人发现;另一方面,这种死法肯定比摔下去要好,因为可以免去了很长一段等死的阶段——只要我别太怕死,双手紧抱住引擎的边缘,以至于一点一点的被吸进去。

 

某次 GR 上看到有人分享宅男必备的几个玩具,其中之一便是 Arduino,手痒,便入手了一个(同时还买了个遥控车,打算拆了改用 Arduino 控制,没到手);然而此刻我正在学校,手头上不但没有发光二极管、蜂鸣器、马达、高压包、电烙铁这些老朋友,甚至连导线都没有,于是唯一能用的输出是 13 号口自带的一个发光二极管。

一个发光二极管,那能玩的就只有摩尔斯电码了:

#define UNIT_TIME 250
#define OUTPUT_PIN 13

static char const mCode[][8]= {
    "-----", // O
    ".----", // 1 
    "..---", // 2
    "...--", // 3
    "....-", // 4
    ".....", // 5
    "-....", // 6
    "--...", // 7
    "---..", // 8
    "----.", // 9

    ".-",    // A
    "-...",  // B
    "-.-.",  // C
    "-..",   // D
    ".",     // E
    "..-.",  // F
    "--.",   // G
    "....",  // H
    "..",    // I
    ".---",  // J
    "-.-",   // K
    ".-..",  // L
    "--",    // M
    "-.",    // N
    "---",   // O
    ".--.",  // P
    "--.-",  // Q
    ".-.",   // R
    "...",   // S
    "-",     // T
    "..-",   // U
    "...-",  // V
    ".--",   // W
    "-..-",  // X
    "-.--",  // Y
    "--.."   // Z
};

void light(int time) {
    digitalWrite(OUTPUT_PIN, HIGH);
    delay(time);
    digitalWrite(OUTPUT_PIN, LOW);
}

void alpha(char c) {
    char const *code=0;
    if(c>='0' && c<='9') {
        code=mCode[c-'0'];
    } else if(c>='a' && c<='z') {
        code=mCode[c-'a'+10];
    } else if(c>='A' && c<='Z') {
        code=mCode[c-'A'+10];
    } else {
        delay(4*UNIT_TIME); // 应该停顿 7 个单位,不过每个单词结束后都已经停顿了 3 个单位时间
        return;
    } 
    for(;*code;++code) {
        switch(*code) {
        case '.':
            light(UNIT_TIME);
            break;
        case '-':
            light(3*UNIT_TIME);
            break;
        }
        delay(UNIT_TIME);
    }
    delay(2*UNIT_TIME);
}

void say(char const* sentence) {
    for(char const* p=sentence; *p; ++p) {
        alpha(*p);
    }
}

void setup() {
    pinMode(OUTPUT_PIN, OUTPUT);
}

void loop() {
    say("I Feel So Alone   ");
}

wikipedia 说同一个单词不同字母间有三个单位的间隔,但我发现这很不好认;接着我就想如果再多买一块板子,接上光敏电阻,一个亮,一个读,岂不是很好玩;接着又想,如果真有两块板子,那我何必用摩尔斯电码呢,用灭灯代表 0,亮灯代表 1,直接传输就好,爱折腾的话想办法用数组保存一个已经预先设定好的霍夫曼编码似乎更可以装B了,接着我又想,那我是不是要想办法让它知道什么时候是对方传输完了,什么时候是在传输一大堆的 0 呢,要不在每组数据末尾加上一个 END,开头加上 BEGIN?可是那我真要传输 BEGIN 这个单词了又怎么办?用 \ 标记?对呀,然后 \ 就表示为 \\,太完美了——可是那突然掉电怎么办,要么每隔一秒种说一次“还没完!”?…………。

Update: 我傻,用校验码啊

 

另外,开始看了看 Haskell,因为它的自我介绍很有意思,大意是 “即使你学了 Haskell 什么实际用途都没有,至少也可以提高你的编程能力”,好吧,这个我相信。

另外就我目前的认识来看 Haskell 推理形式的函数定义方法和 C++ template 元编程简直太像了 @_@ 说 C++ 恐怖果然是有道理的……

 

有人注意到上海科技馆的男厕标识很奇特吗??

几天前翻照片玩,找出了09年10月7号在上海科技馆拍的一张照片,当时我就不理解,目前我还是不理解。

 

iiiiif 拍攝的上海科技馆男厕。

 

如图,求解。

 

——对了,俺这儿 Flickr 有访问障碍,所以看不到图而且又不想那个啥的话:网易 || 原图

也可以考虑围观一下我那个只谈风月的blogbus :: 那一次的上海之行

出门拍照玩

然后发现有个看上去很壮的家伙正扛着小小白拍荷花,自卑死了,遂遁走。

 

IMG_0418

 

IMG_0417

 

IMG_0410

 

 

第一次飞

IMG_0273

 

IMG_0270

 

IMG_0282