きしだのはてな このページをアンテナに追加 RSSフィード

2015-09-07(月) ぼくの実装した最弱のディープラーニング

[][]ぼくの実装した最弱のディープラーニング 14:54 ぼくの実装した最弱のディープラーニング - きしだのはてな を含むブックマーク

8月の頭からディープラーニングを実装していたのを、先日、プレゼンしてきました。

プログラマのための数学勉強会@福岡 - connpass



GPU対応したり、ドロップアウトとかミニバッチとかいろいろ実装して、結構つよくなってます。

ちゃんと学習してくれないこと以外は。

f:id:nowokay:20150907145649p:image


ソースはこんな感じになってきています。

https://github.com/kishida/neuralnet/tree/CorrectOperationAsCCN


GPU対応にはaparapiを使っています。JavaGPUコードが書けるスグレモノです。

aparapi - API for data parallel Java. Allows suitable code to be executed on GPU via OpenCL. - Google Project Hosting

けど、バグがあってはまってたので、修正してパッチ投げてます。

Add parenthesis around conditional expression by kishida · Pull Request #10 · aparapi/aparapi

最近停滞してるっぽくて、取り込まれる気がしない。

※ 9/9 取り込まれました!


参考文献としては、とりあえずこの本。ディープラーニングについて、だいたいわかります。基本的には、この本を参考にして実装しました。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

細かいパラメータやニューラルネットワークの構成などは、ニューラルネットが流行りだすきかっけになった、この論文を参考にしてます。

ImageNet Classification with Deep Convolutional Neural Networks

このふたつを読むと、実装に必要な情報はだいたい揃います。


学習用の画像には、CALTECH256というのを使っています。

http://www.vision.caltech.edu/Image_Datasets/Caltech256/

※ 9/9追記 カテゴリごとの画像数が、80枚だったり700枚だったり、偏ってるので注意


学習結果を保存できるようになったり、ニューラルネットの構成をJSONで記述できるようになったり、だいぶ まっとうなフレームワークになってきた気がします。

2015-08-08(土) 畳み込みニューラルネット、失敗

[][]畳み込みニューラルネット、失敗 00:57 畳み込みニューラルネット、失敗 - きしだのはてな を含むブックマーク

最近流行りのディープラーニングなるものを実装しようと思ってます。

いまやろうとしてるのは、画像認識のための畳み込みニューラルネット(CNN)です。

一応、コードを書いてみるには書いてみたんですけど、失敗。

下のノイズっぽいのが一段目のフィルタ群なんですが、画像を一回学習させただけで白く発散してます。

f:id:nowokay:20150809005153p:image


なんか、数式を勘違いしてるんだと思うんで、もう一回、本を読み直しです。

この本、とりあえず実装に必要なことは全部書いてあると思います。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

けどなんか、レビューで誤植があるっぽいこと書いてありますね。確認してみよう。


今のところのコードはこんな感じです。

https://github.com/kishida/neuralnet/blob/69f789f474534323ecee0ca169000f3715ee3bc2/src/main/java/kishida/imagefiltering/ConvolutionalNet.java

2014-12-08(月) パーセプトロンからSVMでの画像認識まで、機械学習エントリのまとめ

[]パーセプトロンからSVMでの画像認識まで、機械学習エントリのまとめ 10:05 パーセプトロンからSVMでの画像認識まで、機械学習エントリのまとめ - きしだのはてな を含むブックマーク

なんかJJUGナイトセミナーで機械学習をやるっぽくて、定員100人が40人キャンセル待ちとかになってますね。

【東京】JJUG ナイト・セミナー「機械学習・自然言語処理特集!」12/17(水)開催 - 日本Javaユーザーグループ | Doorkeeper

ということで、予習用だか復習(になるかわかんないけど)用に、2008年になんか機械学習をやってたときのエントリをまとめてみます。


今でこそ機械学習はなんかもりあがってるけど、2008年にぼくがやってたとき「ところで機械学習やってなんになるの?」ってよく言われてました。ぼくも「いや、なんかそこに機械学習ってものがあるから実装してる」みたいな答えをしてた気がします。特に目的はありませんでした。

たまたま サポートベクターマシン入門 という本を見かけて、なんか実装してみたくなっただけです。

変な力がありあまってたっぽい。


機械学習ことはじめ

まずは、機械学習ってなんだろう、ってところから始めてます。

それで、わけもわからずパーセプトロンでパターン認識の実装をしてみてます。

パーセプトロンで手書き数字認識(失敗) - きしだのはてな

バックプロパゲーション - きしだのはてな

基本のNearestNeighbors法(NN法)でパターン認識 - きしだのはてな


機械学習の学習

このあたりから、基本から勉強する感じになってますね。

NN法っていいよね - きしだのはてな

3-NN法 - きしだのはてな

パーセプトロンってなんだろう? - きしだのはてな

パーセプトロンで非線形分離するには - きしだのはてな

バックプロパゲーションでニューラルネットの学習 - きしだのはてな


サポートベクターマシン

で、なんとなく機械学習のコンセプトがわかって、数式にも慣れてきたので、サポートベクターマシンの実装を始めています。

線形サポートベクターマシン失敗 - きしだのはてな

非線形サポートベクターマシン - きしだのはてな

ソフトマージンサポートベクターマシン - きしだのはてな

サポートベクターマシンの本 - きしだのはてな


SMOアルゴリズムの実装

とりあえずサポートベクターマシンというのが何かわかって、実際に使える程度の速さで学習ができるようSMOというアルゴリズムを実装しています。

SVMの学習用アルゴリズムSMOを実装してみる - きしだのはてな

SMOの収束速い! - きしだのはてな

ところでサポートベクターマシンって何なの? - きしだのはてな

ガウシアンカーネルのパラメータを自動的に求めてみる - きしだのはてな


サポートベクターマシンの応用

SMOが実装できたので、それを使ってちょっとしたサンプルを作ってます。

「おはよう」とか今起きたっぽい発言に対して「おはよう」と答えるbotを想定。

サポートベクターマシンであいさつbotを作るためのカーネル関数 - きしだのはてな

あいさつbotで2次の多項式カーネルを試してみる。 - きしだのはてな

改めて2次多項式カーネルであいさつbotをやってみる - きしだのはてな


画像処理

CCVというアルゴリズムをaudioswitchさんが紹介されていたので、実装してみました。その紹介エントリはもう見れなくなっててちょっと残念です。

また、そうやって実装したCCVを使って画像検索っぽいものやビール画像判定っぽいものを作ってます。

まあ、この判定はうまくいったところで画面キャプチャしてるし、データが手持ちのものだけだったので、実用ではないですけど。

Color Coherence VectorをJavaで実装してみた - きしだのはてな

Color Coherence Vectorで画像検索を作ってみた - きしだのはてな

画像同士の距離がとれたら近い画像マップができるよね - きしだのはてな

ビール画像判定プログラムできたよ! - きしだのはてな


その後の機械学習

ところで、この時期は結構サポートベクターマシンが期待されてたのですけど、まずデータが多くなると学習にすごく時間がかかるのと、アルゴリズム的に並列化できないのでその時間が短縮できないというのがあって、大規模なデータでは使えないということになったんじゃないかと思います。

また、学習時点でデータがそろってないといけないので、データが徐々に増えるような状況には対応できず、用途にあいにくいというのもある気がします。


そこで今は、バックプロパゲーションを賢く多段にしたような感じのディープラーニングというのが流行ってて、ネコを認識したり画像の説明したりすごいことをやってます。

しかも並列実行やりやすいし、データが徐々に増えていくのにも対応しやすいし、現状の用途にとてもあってます。

なので、これからはディープラーニングを勉強したほうがいいと思うのですが、Twitterに「ディープラーニングのいい入門書ないかな」みたいなこと書いたら、3秒くらいであんちべさんが「ない」と返事してくださいました。ということは、ないんでしょう。

ディープラーニングの概要は、この資料がわかりやすいし「興味あったけど調べるの面倒くさくて諦めた人向け」ということでピッタリです。

Deep learning

この和訳プロジェクトに期待したり貢献したりするとよさそうです。

DeepLearning - ニューラルネットワークと深層学習(和訳) - Qiita


機械学習自体の勉強としては、このあたりが入門にいいですかね。

フリーソフトではじめる機械学習入門

フリーソフトではじめる機械学習入門

ほかにも、いい本がいろいろ出てると思います。


まとめ

というか、これぼく理解不能なんだけど。

なんでこの時期こんなエントリ書けてるか、こんなコード書けてるかまったく意味不明です。いま説明を求められても困ります。

変なもん食べてたんだろうか。そういえばこのころからベルギービールとか飲みだしてるな。

この時期はまだTwitterが広まってなくてコメントが生きているので、そこでいろいろ教えてもらってるのも役に立ってますね。いまだとすぐにガチの人に取り囲まれて萎えてたかもしれません。

ひとつわかるのは、なんか仕事せずにやってるな、ってことですね。

2008-10-10(金) ビール画像判定プログラムできたよ!

ヴェルティンス ピルスナー

[][]ビール画像判定プログラムできたよ! 08:06 ビール画像判定プログラムできたよ! - きしだのはてな を含むブックマーク

画像同士の距離が取れるなら、ビール画像の判定プログラムもできるよね。

ってことで、手持ちの画像をSVMに食わせてみて、ビール画像を判定させてみました。

f:id:nowokay:20081010074944p:image


誤判別?なにそれ


ソースはこんな感じ。

ビールの画像はbeerがついたフォルダかファイル名にしてください。

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.text.*;

public class ColorCoherenceVecotrLaern extends javax.swing.JFrame {

    /** Creates new form ColorCoherenceVecotrLaern */
    public ColorCoherenceVecotrLaern() {
        initComponents();
        System.out.println("CCV計算");
        readFile(new File("C:\\search"));
        System.out.println("カーネルパラメータ");
        solveGaussianParam();
        System.out.println("SMO");
        learn();
        System.out.println("終了");
    }

    @SuppressWarnings("unchecked")
    private void initComponents() {

        javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
        tpResut = new javax.swing.JTextPane();
        javax.swing.JPanel jPanel1 = new javax.swing.JPanel();
        txtSearch = new javax.swing.JTextField();
        javax.swing.JButton btnJudge = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jScrollPane1.setViewportView(tpResut);

        getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);

        txtSearch.setColumns(30);
        jPanel1.add(txtSearch);

        btnJudge.setText("判定");
        btnJudge.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnJudgeActionPerformed(evt);
            }
        });
        jPanel1.add(btnJudge);

        getContentPane().add(jPanel1, java.awt.BorderLayout.NORTH);

        pack();
    }

    private void btnJudgeActionPerformed(java.awt.event.ActionEvent evt) {
        String filename = txtSearch.getText();
        File f = new File(filename);
        try {
            BufferedImage img = ImageIO.read(f);
            int[] ccv = colorCoherenceVector(img);
            int result = trial(ccv);
            int wd = img.getWidth();
            int ht = img.getHeight();
            if(wd > ht){
                ht = 80 * ht / wd;
                wd = 80;
            }else{
                wd = 80 * wd / ht;
                ht = 80;
            }
            BufferedImage thumb = new BufferedImage(wd, ht, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = (Graphics2D) thumb.getGraphics();
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.drawImage(img, 0, 0, wd, ht, this);
            g.dispose();
            StyledDocument sd = tpResut.getStyledDocument();
            SimpleAttributeSet sas = new SimpleAttributeSet();
            StyleConstants.setIcon(sas, new ImageIcon(thumb));
            sd.insertString(sd.getLength(), "画像", sas);
            sd.insertString(sd.getLength(), result > 0 ? "ビールだ!\n" : "なにそれ\n", null);
        }catch(BadLocationException e){
        }catch(IOException e){
            System.out.println(e.getMessage());
        }

    }

    //ファイルのCCVを求める
    private void readFile(File f){
        if(f.isDirectory()){
            File[] files = f.listFiles();
            for(File file : files){
                readFile(file);
            }
        }else{
            try {
                BufferedImage img = ImageIO.read(f);
                if(img == null) return;
                int[] data = colorCoherenceVector(img);
                int i = -1;
                if(f.getCanonicalPath().contains("beer")){
                    i = 1;
                }
                patterns.add(new AbstractMap.SimpleEntry<Integer, int[]>(i, data));
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }

        }
    }

    /** CCVを求める */
    public static int[] colorCoherenceVector(BufferedImage imgsrc){

        int w = imgsrc.getWidth();
        int h = imgsrc.getHeight();
        //サイズ正規化
        int limit = 200;
        if(w < h){
            w = w * limit / h;
            h = limit;
        }else{
            h = h * limit / w;
            w = limit;
        }
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D grp = (Graphics2D) img.getGraphics();
        grp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        grp.drawImage(imgsrc, 0, 0, w, h, null);
        grp.dispose();

        //ガウシアンフィルタ
        int[] ctemp = img.getRGB(0, 0, w, h, null, 0, w);
        int[] ctbl = new int[ctemp.length];
        int[][] filter = {
            {1, 2, 1},
            {2, 4, 2},
            {1, 2, 1}};
        for(int y = 0; y < h; ++y){
            for(int x = 0; x < w; ++x){
                int tr = 0;
                int tg = 0;
                int tb = 0;
                int t = 0;
                for(int i = -1; i < 2; ++i){
                    for(int j = -1; j < 2; ++j){
                        if(y + i < 0) continue;
                        if(x + j < 0) continue;
                        if(y + i >= h) continue;
                        if(x + j >= w) continue;
                        t += filter[i + 1][j + 1];
                        int adr = (x + j) + (y + i) * w;
                        tr += filter[i + 1][j + 1] * ((ctemp[adr] >> 16) & 255);
                        tg += filter[i + 1][j + 1] * ((ctemp[adr] >> 8)  & 255);
                        tb += filter[i + 1][j + 1] * ( ctemp[adr]        & 255);
                    }
                }
                ctbl[x + y * w] = ((tr / t) << 16) + ((tg / t) << 8) + tb / t;
            }
        }

        //減色
        for(int i = 0; i < ctbl.length; ++i){
            int r = (ctemp[i] >> 16) & 192;
            int g = (ctemp[i] >> 8) & 192;
            int b = ctemp[i] & 192;
            ctbl[i] = (r << 16) + (g << 8) + b;
        }

        //タグ付け
        int[][] lbl = new int[w][h];
        int id = 0;
        for(int y = 0; y < h; ++y){
            for(int x = 0; x < w; ++x){
                int col = ctbl[y * w + x];
                if(y > 0){
                    if(x > 0){
                        if(ctbl[(y - 1) * w + x - 1] == col){
                            //左上と一緒
                            lbl[x][y] = lbl[x - 1][y - 1];
                            continue;
                        }
                    }
                    if(ctbl[(y - 1) * w + x] == col){
                        //上と一緒
                        lbl[x][y] = lbl[x][y - 1];
                        continue;
                    }
                    if(x < w - 1){
                        if(ctbl[(y - 1) * w + x + 1] == col){
                            //右上と一緒
                            lbl[x][y] = lbl[x + 1][y - 1];
                            continue;
                        }
                    }
                }
                if(x > 0){
                    if(ctbl[y * w + x - 1] == col){
                        //左と一緒
                        lbl[x][y] = lbl[x - 1][y];
                        continue;
                    }
                }
                lbl[x][y] = id;
                ++id;
            }
        }
        //集計
        int[] count = new int[id];
        int[] color = new int[id];
        for(int x = 0; x < w; ++x){
            for(int y = 0; y < h; ++y){
                count[lbl[x][y]]++;
                color[lbl[x][y]] = ctbl[y * w + x];
            }
        }
        int[] data = new int[129];
        for(int i = 0; i < id; ++i){
            int d = color[i];
            color[i] = (((d >> 22) & 3) << 4) + (((d >> 14) & 3) << 2) + ((d >> 6) & 3);
            if(count[i] < 20){
                data[color[i] * 2 + 1] ++;
            }else{
                data[color[i] * 2] ++;
            }
        }
        return data;
    }


    double sig = 35;
    /** カーネルパラメータを求める */
    void solveGaussianParam(){
        double s = 0;
        int count = 0;
        for(Map.Entry<Integer, int[]> p1 : patterns){
            double m = Double.MAX_VALUE;
            for(Map.Entry<Integer, int[]> p2 : patterns){
                double d = 0;
                for(int i = 0; i < p1.getValue().length; ++i){
                    int t = p1.getValue()[i] - p2.getValue()[i];
                    d += t * t;
                }
                if(d == 0) continue;
                if(d < m) m = d;
            }
            if(m == Double.MAX_VALUE) continue;
            s += m;
            ++count;
        }
        sig = Math.sqrt(s / count) * 2;
        System.out.println(sig);
    }

    double kernel(int[] x1, int[] x2){
        //ガウシアンカーネル
        int total = 0;
        for(int i = 0; i < x1.length; ++i){
            total += (x1[i] - x2[i]) * (x1[i] - x2[i]);
        }
        return Math.exp(-total / (sig * sig));
    }

    double[] w;//係数
    double b;//バイアス
    final double c = 10;//許容範囲?無限大にするとハードマージンになるはずだけど
    final double tol = 0.7;//KKT条件の許容範囲(1 - ε)
    double[] lambda;
    double z = 0;
    List<Map.Entry<Integer, int[]>> patterns =
            new ArrayList<Map.Entry<Integer, int[]>>();
    public void learn() {
        w = new double[patterns.size()];
        b = 0;

        lambda = new double[patterns.size()];
        for(int i = 0; i < lambda.length; ++i){
            lambda[i] = 0;
        }

        //未定乗数を求める
        boolean alldata = true;//すべてのデータを処理する場合
        boolean changed = false;//変更があった
        eCache = new double[patterns.size()];
        int lp;
        for(lp = 0; lp < 500000 && (alldata || changed); ++lp)  {
            changed = false;
            z = 0;
            boolean lastchange = true;
            PROC_LOOP:
            for(int j = 0; j < patterns.size(); ++j){
                //基準点2を選ぶ
                double alpha2 = lambda[j];
                if(!alldata && (alpha2 <= 0 || alpha2 >= c)){// 0 < α < C の点だけ処理する
                    continue;
                }
                if(lastchange){
                    //初回やデータがかわったときキャッシュのクリア
                    for(int i = 0; i < eCache.length; ++i) eCache[i] = Double.NaN;
                }
                lastchange = false;

                int t2 = patterns.get(j).getKey();
                double fx2 = calcE(j);

                //KKT条件の判定
                double r2 = fx2 * t2;
                if(!((alpha2 < c && r2 < -tol) || (alpha2 > 0 && r2 > tol))){//KKT条件をみたすなら処理しない
                    continue;
                }
                //基準点1を選ぶ
                //選択法1
                int i = 0;
                int offset = (int)(Math.random() * patterns.size());

                double max = -1;
                for(int ll = 0; ll < patterns.size(); ++ll){//全データにつき
                    int l = (ll + offset) % patterns.size();
                    //0 < α < C
                    if(0 >= lambda[l] || c <= lambda[l]) continue;
                    double dif = Math.abs(calcE(l) - fx2);
                    if(dif > max){
                        max = dif;
                        i = l;
                    }
                }
                if(max >= 0){
                    if(step(i, j)){
                        //処理をしたら次へ
                        changed = true;
                        lastchange = true;
                        continue PROC_LOOP;
                    }
                }
                //選択法2
                offset = (int)(Math.random() * patterns.size());//ランダムな位置から
                for(int l = 0; l < patterns.size(); ++l){
                    //0 < α < C
                    i = (l + offset) % patterns.size();
                    if(0 >= lambda[i] || c <= lambda[i]) continue;
                    if(step(i, j)){
                        //処理をしたら次へ
                        changed = true;
                        lastchange = true;
                        continue PROC_LOOP;
                    }
                }
                //選択法3
                offset = (int)(Math.random() * patterns.size());//ランダムな位置から
                for(int l = 0; l < patterns.size(); ++l){
                    i = (l + offset) % patterns.size();
                    if(step(i, j)){
                        //処理をしたら次へ
                        changed = true;
                        lastchange = true;
                        continue PROC_LOOP;
                    }
                }
            }

            ////すべてのデータを処理しても処理するものがなければ終了になる
            if(z < 0.01) changed = false;
            if(alldata){
                alldata = false;
            }else{
                if(changed) alldata = true;
            }
        }
        //System.out.println(lp);

        //wの値を求める
        for(int i = 0; i < w.length; ++i){
            w[i] = lambda[i] * patterns.get(i).getKey();
        }
        //bを求める
        b = 0;
        int count = 0;
        for(int i = 0; i < lambda.length; ++i){
            if(Math.abs(w[i]) <= 0.05) continue;
            for(int l = 0; l < w.length; ++l){
                b -= w[l] * kernel(
                        patterns.get(i).getValue(), patterns.get(l).getValue());
            }
            ++count;
        }
        b /= count;
    }

    public int trial(int[] data) {
        double s = b;
        for(int i = 0; i < w.length; ++i){
            Map.Entry<Integer, int[]> p = patterns.get(i);
            s += w[i] * kernel(data, p.getValue());
        }
        return s > 0 ? 1 : -1;
    }

    private double[] eCache;
    private double calcE(int i){
        if(!Double.isNaN(eCache[i])) return eCache[i];
        double e = b - patterns.get(i).getKey();
        for(int j = 0; j < lambda.length; ++j){
            e += lambda[j] * patterns.get(j).getKey() *
                    kernel(patterns.get(j).getValue(), patterns.get(i).getValue());
        }
        eCache[i] = e;
        return e;
    }

    /** 実際の計算処理 */
    private boolean step(int i, int j) {
        if(i == j) return false;
        double fx2 = calcE(j);

        int t1 = patterns.get(i).getKey();
        int t2 = patterns.get(j).getKey();

        double fx1 = calcE(i);

        //基準点2を計算
        double k11 = kernel(patterns.get(i).getValue(), patterns.get(i).getValue());
        double k22 = kernel(patterns.get(j).getValue(), patterns.get(j).getValue());
        double k12 = kernel(patterns.get(i).getValue(), patterns.get(j).getValue());
        double eta = k11 + k22 - 2 * k12;
        if(eta <= 0) return false;
        double newwj = lambda[j] + t2 * (fx1 - fx2) / eta;
        //クリッピング
        double u;
        double v;
        if(t1 == t2){
            u = Math.max(0, lambda[j] + lambda[i] - c);
            v = Math.min(c, lambda[j] + lambda[i]);
        }else{
            u = Math.max(0, lambda[j] - lambda[i]);
            v = Math.min(c, c + lambda[j] - lambda[i]);
        }
        if(u == v) return false;
        newwj = Math.max(u, newwj);
        newwj = Math.min(v, newwj);

        //基準点2から基準点1を計算
        z += Math.abs(lambda[j] - newwj);
        lambda[i] += t1 * t2 * (lambda[j] - newwj);
        lambda[j] = newwj;
        return true;
    }

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new ColorCoherenceVecotrLaern().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify
    private javax.swing.JTextPane tpResut;
    private javax.swing.JTextField txtSearch;
    // End of variables declaration

}

しましましましま 2008/10/10 22:54 画像に写っている対象物を識別する問題は,画像認識で最近またホットな研究課題となっているようです.
http://mm.cs.uec.ac.jp/
のサーベイ「一般物体認識の現状と今後」はすばらしい参考資料です.

nowokaynowokay 2008/10/11 21:18 お〜、この資料はおもしろそうですね。ちゃんと読んでみます。

2008-10-09(木) 画像同士の距離がとれたら近い画像マップができるよね

鬼太郎ビール ヴァイツェン

[][]画像同士の距離がとれたら近い画像マップができるよね 06:03 画像同士の距離がとれたら近い画像マップができるよね - きしだのはてな を含むブックマーク

CCVで画像同士の距離が取れたってことは、画像同士どれが近いかというマップができるよね。

f:id:nowokay:20081009055757p:image


ソースはこんな感じで

import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;

public class ColorCoherenceVectorMap extends javax.swing.JFrame {

    /** Creates new form ColorCoherenceVectorMap */
    public ColorCoherenceVectorMap() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        java.awt.Dimension screenSize = 
            java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((screenSize.width-800)/2, (screenSize.height-600)/2, 800, 600);

        readFile(new File("C:\\search"));

        final int[][] dists = new int[images.size()][images.size()];
        for(int i = 0; i < images.size(); ++i){
            int[] ccv1 = images.get(i).getValue();
            for(int j = i + 1; j < images.size(); ++j){
                int[] ccv2 = images.get(j).getValue();
                int total = 0;
                for(int k = 0; k < ccv1.length; ++k){
                    total += Math.abs(ccv1[k] - ccv2[k]);
                }
                dists[i][j] = total;
                dists[j][i] = total;
            }
        }

        final BufferedImage img = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);
        final JComponent comp = new JComponent() {
            @Override
            protected void paintComponent(Graphics g) {
                g.drawImage(img, 0, 0, this);
            }
        };
        add(comp);

        final Point2D[] pos = new Point2D[images.size()];
        Random r = new Random();
        final Image[] thumbs = new Image[pos.length];
        int limit = 40;
        for(int i = 0; i < pos.length; ++i){
            pos[i] = new Point2D.Double(r.nextInt(800), r.nextInt(600));
            try {
                BufferedImage im = ImageIO.read(new File(images.get(i).getKey()));
                int w = im.getWidth();
                int h = im.getHeight();
                if(w > h){
                    h = limit * h / w;
                    w = limit;
                }else{
                    w = limit * w / h;
                    h = limit;
                }
                BufferedImage imt = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
                Graphics2D g = (Graphics2D) imt.getGraphics();
                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g.drawImage(im, 0, 0, w, h, this);
                g.dispose();
                thumbs[i] = imt;
            } catch (IOException ex) {
            }
        }

        new Thread(){
            @Override
            public void run() {
                long t = System.currentTimeMillis();
                for(;;){
                    for(int i = 0; i < pos.length; ++i){
                        for(int j = 0; j < pos.length; ++j){
                            if(i == j) continue;
                            double dx = pos[i].getX() - pos[j].getX();
                            double dy = pos[i].getY() - pos[j].getY();
                            if(dx == 0 && dy == 0){
                                pos[i].setLocation(pos[i].getX() + 1, pos[i].getY() + 1);
                                dx = 1;
                                dy = 1;
                            }
                            double d = Math.sqrt(dx * dx + dy * dy);
                            int nd = dists[i][j] / 8 + 15;
                            dx = (int)(dx * 2 / d);
                            dy = (int)(dy * 2 / d);
                            if(d > nd){
                                pos[i].setLocation(pos[i].getX() - dx, pos[i].getY() - dy);
                            }else{
                                pos[i].setLocation(pos[i].getX() + dx, pos[i].getY() + dy);
                            }
                        }
                    }
                    double mdx = 0;
                    double mdy = 0;
                    for(int i = 0; i < pos.length; ++i){
                        mdx += pos[i].getX();
                        mdy += pos[i].getY();
                    }
                    mdx = 400 - mdx / pos.length;
                    mdy = 300 - mdy / pos.length;
                    for(int i = 0; i < pos.length; ++i){
                        pos[i].setLocation(pos[i].getX() + mdx, pos[i].getY() + mdy);
                    }

                    long nt = System.currentTimeMillis();
                    if(nt - t > 200){
                        t = nt;
                        Graphics2D g = (Graphics2D) img.getGraphics();
                        g.setColor(Color.WHITE);
                        g.fillRect(0, 0, 800, 600);
                        g.setColor(Color.BLACK);
                        for(int i = 0; i < pos.length; ++i){
                            g.drawImage(thumbs[i], (int)pos[i].getX(), (int)pos[i].getY(), ColorCoherenceVectorMap.this);
                        }
                        g.dispose();
                        comp.repaint();
                    }
                }
            }

        }.start();
    }

    //ファイルのCCVを求める
    private void readFile(File f){
        if(f.isDirectory()){
            File[] files = f.listFiles();
            for(File file : files){
                readFile(file);
            }
        }else{
            try {
                BufferedImage img = ImageIO.read(f);
                if(img == null) return;
                int[] data = colorCoherenceVector(img);
                images.add(new AbstractMap.SimpleEntry(f.getCanonicalPath(), data));
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }

        }
    }

    List<Map.Entry<String, int[]>> images = new ArrayList<Map.Entry<String, int[]>>();

    /** CCVを求める */
    public static int[] colorCoherenceVector(BufferedImage imgsrc){

        int w = imgsrc.getWidth();
        int h = imgsrc.getHeight();
        //サイズ正規化
        int limit = 200;
        if(w < h){
            w = w * limit / h;
            h = limit;
        }else{
            h = h * limit / w;
            w = limit;
        }
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D grp = (Graphics2D) img.getGraphics();
        grp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        grp.drawImage(imgsrc, 0, 0, w, h, null);
        grp.dispose();

        //ガウシアンフィルタ
        int[] ctemp = img.getRGB(0, 0, w, h, null, 0, w);
        int[] ctbl = new int[ctemp.length];
        int[][] filter = {
            {1, 2, 1},
            {2, 4, 2},
            {1, 2, 1}};
        for(int y = 0; y < h; ++y){
            for(int x = 0; x < w; ++x){
                int tr = 0;
                int tg = 0;
                int tb = 0;
                int t = 0;
                for(int i = -1; i < 2; ++i){
                    for(int j = -1; j < 2; ++j){
                        if(y + i < 0) continue;
                        if(x + j < 0) continue;
                        if(y + i >= h) continue;
                        if(x + j >= w) continue;
                        t += filter[i + 1][j + 1];
                        int adr = (x + j) + (y + i) * w;
                        tr += filter[i + 1][j + 1] * ((ctemp[adr] >> 16) & 255);
                        tg += filter[i + 1][j + 1] * ((ctemp[adr] >> 8)  & 255);
                        tb += filter[i + 1][j + 1] * ( ctemp[adr]        & 255);
                    }
                }
                ctbl[x + y * w] = ((tr / t) << 16) + ((tg / t) << 8) + tb / t;
            }
        }

        //減色
        for(int i = 0; i < ctbl.length; ++i){
            int r = (ctemp[i] >> 16) & 192;
            int g = (ctemp[i] >> 8) & 192;
            int b = ctemp[i] & 192;
            ctbl[i] = (r << 16) + (g << 8) + b;
        }

        //タグ付け
        int[][] lbl = new int[w][h];
        int id = 0;
        for(int y = 0; y < h; ++y){
            for(int x = 0; x < w; ++x){
                int col = ctbl[y * w + x];
                if(y > 0){
                    if(x > 0){
                        if(ctbl[(y - 1) * w + x - 1] == col){
                            //左上と一緒
                            lbl[x][y] = lbl[x - 1][y - 1];
                            continue;
                        }
                    }
                    if(ctbl[(y - 1) * w + x] == col){
                        //上と一緒
                        lbl[x][y] = lbl[x][y - 1];
                        continue;
                    }
                    if(x < w - 1){
                        if(ctbl[(y - 1) * w + x + 1] == col){
                            //右上と一緒
                            lbl[x][y] = lbl[x + 1][y - 1];
                            continue;
                        }
                    }
                }
                if(x > 0){
                    if(ctbl[y * w + x - 1] == col){
                        //左と一緒
                        lbl[x][y] = lbl[x - 1][y];
                        continue;
                    }
                }
                lbl[x][y] = id;
                ++id;
            }
        }
        //集計
        int[] count = new int[id];
        int[] color = new int[id];
        for(int x = 0; x < w; ++x){
            for(int y = 0; y < h; ++y){
                count[lbl[x][y]]++;
                color[lbl[x][y]] = ctbl[y * w + x];
            }
        }
        int[] data = new int[129];
        for(int i = 0; i < id; ++i){
            int d = color[i];
            color[i] = (((d >> 22) & 3) << 4) + (((d >> 14) & 3) << 2) + ((d >> 6) & 3);
            if(count[i] < 20){
                data[color[i] * 2 + 1] ++;
            }else{
                data[color[i] * 2] ++;
            }
        }
        return data;
    }

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new ColorCoherenceVectorMap().setVisible(true);
            }
        });
    }
}

audioswitchaudioswitch 2008/10/10 00:19 お〜連休にでもやろうかと思ってたことを…先を越されてしまいました〜ヽ(゜口゜ヽ;)

nowokaynowokay 2008/10/10 08:30 やっちゃいました!
左上に全然違う画像(青いの)がまぎれてたりするのは、見なかったことに。