Pages

Sunday, June 13, 2010

Mosaic

以前看Matrix67大牛介绍过用Mathematica制作马赛克拼贴图像的东东,然后自己也试了一下下
我的故事是,今天灵光一闪,我想到其实每个小图片包含的信息是很多的,只取一个平均值真是太委屈它了,用“高级一点的”图像近似度计算方法肯定可以得到不错的效果,于是我自己瞎掰了一个匹配算法:取缩略图;高斯模糊;比较方差——发现效果果真(相对)还行:









不过试图将这算法用在彩色图片上时,效果相当不靠谱:



嗯……核心代码如下:

// vi:foldmethod=marker
// Author: if ( www.if-yu.info)
// Version: 0.0.3.100613
// Date: June 13rd, 2010
// Copyright (C): if
// License: BSD

/*
 change log:

 * Version 0.0.3:
     can see color

 * Version 0.0.2:
     got an command-line interface

 * Version 0.0.1:
     born

 */

// misc {{{
#include <exception>
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <cmath>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>

#include <opencv/cv.h>
#include <opencv/cv.hpp>
#include <opencv/cxcore.h>
#include <opencv/cxcore.hpp>
#include <opencv/highgui.h>

#include "sizedpqueue.hpp"

using namespace std;
namespace fs = boost::filesystem;
namespace po = boost::program_options;

int g_thumb_size = 12;
int g_image_size = 48;
int g_top_n   = 13;
bool g_color = false;

typedef struct Image {
    cv::Mat thumb;
    cv::Mat orig;
} Image;

typedef struct Record {
    double d;
    int    idx;
} Record;
class RecordCmp {
public:
    bool operator()(Record const& a, Record const& b)const{
        return a.d<b.d;
    }
};
// }}}

// mean idea:
// calculate a `distance' between two images, which shows their similarity {{{
double dist(cv::Mat const& m1, cv::Mat const& m2) {
    assert(m1.type() == m2.type() && m1.size() == m2.size());

    if(CV_MAT_TYPE(m1.type())==CV_8UC1) {
        typedef unsigned char uchar;
        int i,I=m1.rows,j,J=m1.cols,t;
        double d=0;
        for(i=0;i<I;++i)
            for(j=0;j<J;++j) {
                t=int(m1.at<uchar>(i,j))-int(m2.at<uchar>(i,j));
                t*=t;
                d+=/*fabs*/(t);
            }
        return d;
    } else if(CV_MAT_TYPE(m1.type())==CV_8UC3) {
        double dM=0.0, t, d[3]={0.};
        cv::Scalar mean1 = cv::mean(m1);
        cv::Scalar mean2 = cv::mean(m2);
        for(int c=0;c<3;++c) {
            t=mean1.val[c]-mean2.val[c];
            t*=t;
            dM+=t;
        }

        for(int i=0, I=m1.rows; i<I; ++i) {
            for(int j=0, J=m1.cols; j<J; ++j) {
                typedef cv::Vec<uchar,3> BGR_color;
                BGR_color c1=m1.at<BGR_color>(i,j);
                BGR_color c2=m2.at<BGR_color>(i,j);

                for(int c=0;c<3;++c) {
                    t = c1[c]-c2[c];
                    d[c] += t*t;
                }
            }
        }

#if 0
        // compare their histogram
        int hist1[3][64]={{0}}, hist2[3][64]={{0}};
        for(int i=0,I=m1.rows; i<I; ++i) {
            for(int j=0,J=m1.cols; j<J; ++j) {
                typedef cv::Vec<uchar,3> BGR_color;
                BGR_color c1 = m1.at<BGR_color>(i, j);
                BGR_color c2 = m2.at<BGR_color>(i, j);
                for(int c=0;c<3;++c) {
                    ++hist1[c][c1[c]/4];
                    ++hist2[c][c2[c]/4];
                }
            }
        }
        // algorithms:
        // 全不靠谱……
        // correlation
        {
            double a=0.,b=0.,up[3]={0.},down[3]={0.},s1[3]={0.},s2[3]={0.};
            double avg1[3]={0}, avg2[3]={0};
            for(int c=0;c<3;++c) {
                for(int i=0;i<64;++i) {
                    avg1[c] += hist1[c][i]/64.;
                    avg2[c] += hist2[c][i]/64.;
                }
            }
            for(int c=0;c<3;++c){
                for(int i=0;i<64;++i) {
                    a=hist1[c][i]-avg1[c];
                    b=hist2[c][i]-avg2[c];
                    up[c] += a*b;
                    s1[c] += a*a;
                    s2[c] += b*b;
                }
            }
            for(int c=0;c<3;++c){
                down[c] = sqrt(s1[c]*s2[c]);
                d[c] = up[c]/down[c];
            }
        }
        /*
        // so-called chi-square, sucks
        for(int i=0;i<64;++i){
            for(int c=0;c<3;++c) {
                t=(hist1[c][i]-hist2[c][i]);
                t*=t;
                d[c] += t/(hist1[c][i]+hist2[c][i]);
            }
        }
        */
#endif
        t = 0;
        for(int i=0;i<3;++i){
            t+=d[i]*d[i];
        }
        return 0.7*dM + 0.3*t;
    } else {
        // only support CV_8UC1 and CV_8UC3 so far
        assert(CV_MAT_TYPE(m1.type()) == CV_8UC1 || CV_MAT_TYPE(m1.type()) == CV_8UC3); // false;
    }
    return 0;
}
// }}}

// core features: {{{
void addToPool(std::vector<Image>& pool, string const& dir) {
    cv::Size expect(g_image_size, g_image_size);
    cout<<"----- reading "<<(g_color?"color":"gray")<<" images from \""<<dir<<"\" -----"<<endl;
    for(fs::directory_iterator i(dir),I;i!=I;++i){
        Image img;
        img.orig = cv::imread(i->path().string(), g_color?1:0);
        if(img.orig.size()!=expect) {
            cout<<"X";
            continue;
        }
        cv::resize(img.orig, img.thumb, cv::Size(g_thumb_size, g_thumb_size));
        // cv::blur(img.thumb, img.thumb, cv::Size(2,2));
        pool.push_back(img);
        cout<<'+';
    }
    cout<<"\n [done]"<<endl;
}

int chooseRandomFromPool(vector<Image> const& pool, cv::Mat const& source) {
    int a=0,c=pool.size();
    sizedpq<Record,RecordCmp> top(g_top_n);
    cv::Mat thumb;
    cv::resize(source, thumb, cv::Size(g_thumb_size, g_thumb_size));
    for(a=0;a<c;++a) {
        Record r;
        r.d=dist(pool[a].thumb, thumb);
        r.idx=a;
        top.insert(r);
    }
    return top[rand()%g_top_n].idx;
}

void doRandomMosaic(vector<Image> const& pool, cv::Mat const& src, cv::Mat & dst){
    cv::Size size(src.size());
    size.width  /= g_thumb_size;
    size.height /= g_thumb_size;
    cv::Size dstSize( size.width*g_image_size, size.height*g_image_size);
    if(dst.size()!=dstSize) {
        dst = cv::Mat::zeros(dstSize, g_color?CV_8UC3:CV_8UC1);
    }
    cout<<"----- doing what you want me to -----"<<endl;
    if (dstSize.width>800||dstSize.height>600) {
        cout<<"image is too big, i do not want to display it."<<endl;
    }
    for(int i=0,I=size.height; i<I; ++i) {
        for(int j=0,J=size.width; j<J; ++j) {
            cv::Rect r1(j*g_thumb_size,i*g_thumb_size,g_thumb_size,g_thumb_size);
            cv::Rect r2(j*g_image_size,i*g_image_size,g_image_size,g_image_size);
            cv::Mat s(src, r1);
            cv::Mat d(dst, r2);
            int n = chooseRandomFromPool(pool, s);
            pool[n].orig.copyTo(d);
            cout<<'.';
            if(!(dstSize.width>800||dstSize.height>600)) {
                cv::imshow("dst", dst);
                cv::waitKey(10);
            }
        }
        double precence = double(i+1)/(size.height)*100.0;
        cout<<" [ "<<setw(6)<<setprecision(2)<<fixed<<precence<<"% ]\n";
    }
    cout<<"\n [done]"<<endl;
}
// }}}

// interface (if it can be called an interface) {{{
void errorOut(string const& msg) {
    cerr<<msg<<endl;
    exit(1);
}

cv::Size calcDisplaySize(cv::Size sz)
{
    int w = sz.width,
        h = sz.height;
    int w2 = w,
        h2 = h;
    if (w>=h) {
        if(w>700) {
            w2= 700;
            h2= h * w2 / w;
        }
    } else {
        if (h>700) {
            h2= 700;
            w2= w * h2 / h;
        }
    }
    return cv::Size(w2,h2);
}

void display(cv::Mat const& img)
{
    cv::Mat m;
    cv::Size s=calcDisplaySize(img.size());
    if(s!=img.size()) {
        cv::resize(img,m,s);
        cv::imshow("display", m);
    } else {
        cv::imshow("display", img);
    }
    cv::waitKey(0);
}

int main(int argc, char *argv[]) {
    srand(time(0));
    vector<Image> pool;
    vector<string> poolPathes;
    string inPath;
    string outPath;
    double zoom=1.0;

    // boost::program_options is cute!
    po::variables_map vm;
    po::options_description desc("options");
    try{
        desc.add_options()
            ("help,h", "show this message")
            ("color,c", "generate colored mosaic")
            ("zoom,z", po::value<double>(&zoom)->default_value(zoom), "zoom rate")
            ("pool,p", po::value<vector<string> >(&poolPathes), "image pool")
            ("image-size,S", po::value<int>(&g_image_size)->default_value(g_image_size),
                "size of small images")
            ("thumb-size,s", po::value<int>(&g_thumb_size)->default_value(g_thumb_size),
                "size of thumbnails to be used for compairing")
            ("input,i", po::value<string>(&inPath), "input image file")
            ("output,o", po::value<string>(&outPath), "output image file")
            ("top-n,t", po::value<int>(&g_top_n)->default_value(g_top_n), "choose a random piece from top-n like ones")
            ("display,d", "Display the result image")
            ("open,x", "View the result image using associated program in your system")
        ;
        po::positional_options_description p;
        p.add("input", -1);
        po::store(po::command_line_parser(argc,argv)
                     .options(desc).positional(p).run(),
                  vm);
        po::notify(vm);
    } catch(exception const& e) {
        errorOut(e.what());
    }
    if(vm.count("help")) {
        cout<<desc<<endl;
        return 0;
    }
    if(! vm.count("input")) {
        errorOut("No input file");
    }
    if(! vm.count("output")) {
        errorOut("No output file");
    }
    if( vm.count("color")) {
        g_color = true;
    }
    if(! vm.count("pool")) {
        errorOut("Where can i find pieces to make mosaic?");
    } else {
        for(vector<string>::const_iterator citr = poolPathes.begin();
            citr != poolPathes.end();
            ++citr )
        {
            if(!fs::exists(*citr)) {
                errorOut("What is \""+*citr+"\" ??");
            } else if( fs::is_directory(*citr)) {
                addToPool(pool, *citr);
            }
        }
        if(pool.empty()) {
            errorOut("What do you expect when u got no image in pool?");
        }
    }
    if( g_image_size<g_thumb_size ) {
        errorOut("You are doing something foolish, human.");
    }

    cv::Mat src;
    cv::Mat dst;
    try{
        if(g_color) {
            cout<<"Reading source image [colored] "<<endl;
            src = cv::imread(inPath, 1);
        } else {
            cout<<"Reading source image [b&w]"<<endl;
            src = cv::imread(inPath, 0);
        }
    } catch(...) {
        errorOut(string("Can\'t read your input file: \"")+inPath+"\"");
    }
    cv::Size s(src.size());
    if(s==cv::Size(0,0))
        errorOut(string("Can\'t read your input file: \"")+inPath+"\"");
    s.width = static_cast<int>(s.width/g_image_size*g_thumb_size*zoom);
    s.width -= s.width%g_thumb_size;
    s.height = static_cast<int>(s.height/g_image_size*g_thumb_size*zoom);
    s.height -= s.height%g_thumb_size;
    if(s!=src.size()) {
        cv::resize(src, dst, s);
        src=dst.clone();
    }
    doRandomMosaic(pool, src, dst);
    try{
        cv::imwrite(outPath,dst);
    } catch(...) {
        errorOut("Failed to save the image, please check your RP.");
    }

    if(vm.count("display")) {
        display(dst);
    }
    if(vm.count("open")) {
        system(outPath.c_str());
    }

    return 0;
}
// }}}

编译成 mosaic.exe 之后,用法如下:
mosaic <源文件> -o <输出图像> <选项们>
选项有:
  • -p <缩略图库目录> (支持多个目录,至少得要有一个非空的目录)
  • -S <缩略图大小> (单位为像素,默认48)
  • -s <用于比较的缩略图大小> (将会把缩略图再缩率成这个模样,然后用于比较,单位像素,默认12)
  • -z <放大倍数> (实数,默认为 1)
  • -c (启用彩色,默认灰白)
  • -t <n> (从前n个图像中随机选一个用于填充,默认13)
  • -x (完成后在系统关联程序中打开)
  • -d (完成后显示一下)
一个简单的例子是: mosaic -p pool in.jpg -o out.jpg

No comments:

Post a Comment