-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1367 lines (1095 loc) · 84.7 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[名字]]></title>
<link href="http://kkx.github.com/atom.xml" rel="self"/>
<link href="http://kkx.github.com/"/>
<updated>2013-06-09T02:51:34-07:00</updated>
<id>http://kkx.github.com/</id>
<author>
<name><![CDATA[小逸]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[高度相似图片检测: 4基于图片不变特征点]]></title>
<link href="http://kkx.github.com/blog/2012/12/27/gao-du-xiang-si-tu-pian-jian-ce-4ji-yu-tu-pian-bu-bian-te-zheng-dian/"/>
<updated>2012-12-27T13:44:00-08:00</updated>
<id>http://kkx.github.com/blog/2012/12/27/gao-du-xiang-si-tu-pian-jian-ce-4ji-yu-tu-pian-bu-bian-te-zheng-dian</id>
<content type="html"><![CDATA[<p>上面三篇高度相似图片检测没有讲到的一个方法就是基于图片特征点这个feature做图片相似检测。 图片特征点源于1999年Lowe 那篇”Object recognition from local scale-invariant features”
该文可以说是在图像识别形成了一前一后的格局,几年之后各个基于Lowe特征点的方法出来了,而原来基于颜色或者texture的方法就相对于naive了。以后的几年各种优化方法都出来了。
Lowe的方法是Scale-invariant feature transform 简称SIFT,从名字上来看这个方法对于图片的大小变化能起到很好忽略。这里有一点就是,这些特征点的主要目的就是
两张类似图片上的类似物体的特征点值是类似的,而不相似物体区域的特征点值是不一样的。在检测特征点优劣的时候主要就是检测一个repeatability。 Repeatability 就是只类似的物体特征点,
在经历不同的transformation之后的取回率,当然取回率高不一定好,还要看false positive,就类似信息检索。 transformation有很多种: 大小,旋转,颜色亮度,affine转变,occlusion什么的。
各种方法在针对不同的变换有不同的优化。</p>
<p>市面上有很多图片特征检测器和变种,下面是我用过的一些。
<img class="right" src="http://upload.wikimedia.org/wikipedia/commons/5/5f/Corner.png" width="300" height="300" title="角" /></p>
<ul>
<li>Canny: 1986年出来的边检测器。现在还一直用,但不属于特征点范畴,更多用于基于boundary的方法。</li>
<li>Harris corner detector: 边角检测器</li>
<li>SIFT: 虽然这么叫,其实是通过高斯差获取scale space的最大最小值的位置。</li>
<li>SURF: 类似SIFT,速度更快点。</li>
<li>Harris-Affine 和 Hessian Affine: Mikolajczyk 04年的state of the art 方法。</li>
</ul>
<p>检测器检测出来的特征点要去’形容’它,把他转变成特征向量。最长用的就是SIFT descriptor,不局限于sift检测器,上述方法都可以用它,只要知道
特征点的位置,大小。一般检测特征点,会在不同的scale space上,每个scale space出来的特征点大小会不一样。SIFT特征向量是一个维度128的向量,再加上一个
特征区域的角度。 如下图所示,中心点周围的有4个16<em>16的格子,每个格子里有16个4</em>4的格子,会计算每个小格子的方向,划分成8个方向,每个4x4的小个子会成成一个长度为8
的向量,那总共有16个这样4*4大小的格子,所以最后会合成一个维度为128的向量。
<img class="center" src="http://kkx.github.com/images/keypointdescriptor1.JPG" width="600" height="600" title="SIFT" /></p>
<p>通过获得sift向量。一张图片能很好的给压缩成几k的向量矩阵。在检测相似图片的时候,对两张图片的sift向量逐一比对,如果距离在特定阀值之下,就能确定是相似的图片。
但是这样的话计算量是很大的, 因为通常一张1200*960的图片会有700-2000的sift特征点。那如果我的数据库里有1000张图片要比对的话,那就是很大个开销,
而且,sift向量逐一比对的时候,需要计算每对之间的距离。所以很多时候,会通过bag of words 把sift向量分类成x个cluster (x小于sift向量数), 然后把一张图片通过
它在每个cluster所拥有的sift向量数目来形容。这样一张图片就变成了一个长度为x的直方图。这样比对起来就会快了。虽然这样也会有很多信息丢失。但在实践中,这招非常有用。</p>
<p>基于特征点的方法的有点是对于旋转,affine上的变换会比前几个方法容忍度更高,在尺寸,亮度上的容忍也非常棒, 而且如果出现物体的遮盖occlusion,有时候也能检测出来。就是可能速度上没有前者的快。
这个特征点的方法不错,不过有一个致命的缺点就是所谓的semantic gap,往往你有很多你有很多sift的cluster,但是他们的空间信息都是丢失掉的。并且,一个sift和另一个sift之间的相对
距离也不会给利用到。会出现false positive,就是图片和另外一个外形上外向不相似的图片的相似度很高。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[关于Knn和Random Forests的感觉]]></title>
<link href="http://kkx.github.com/blog/2012/12/02/guan-yu-knnhe-random-forestsde-gan-jue/"/>
<updated>2012-12-02T21:25:00-08:00</updated>
<id>http://kkx.github.com/blog/2012/12/02/guan-yu-knnhe-random-forestsde-gan-jue</id>
<content type="html"><![CDATA[<p>家里没网,只好扯扯淡了。</p>
<p>虽然一个是无监督学习,一个是监督学习,但是我感觉两个算法是近亲。 knn的就是找最近友邻,通过数据点和数据点之间的位置关系,而随机森林的话,是通过信息熵把类似的数据都放在一个叶子上。 其实这个感觉以前看到在集智俱乐部(其实就去过一次)有一面之缘的大牛的豆瓣<a href="http://www.douban.com/note/212245564/">博客</a>上写到的,但是当时没有领悟到里面的缘由。毕业论文用到的分类算法就这两个,现在就觉着他们是近亲。</p>
<p>两个算法各有优缺点,先说knn吧,不需要训练,只要有个好点的数据结构储存数据让查询的效率高一些就好(以前的博客有写),找到的总是相似度最高的友邻。这样的好处就是不”吃亏”啦,没有loss。唯一要确定的的参数也就是取多少友邻介一个,这种naive算法给像我这种初学机器学习吐阳图性破的人用再好不过了。不过在测试的时候的速度是knn的软肋。</p>
<p>那随机森林呢,你要随个鸡鸡森林的话,设置起来就难啦,首先森林是基于树木的,你要学会ID3, C5(不是炸弹)决策树什么的,信息熵,gini啊什么的,各种数据分割的criteria,麻烦的很,要设置参数也多,什么多少层熟,一个森林多少树等等。不过参数多也有好处,就是可适性高。随机森林的话,有点在于速度超级快,从root到leaf的这段过程就是一个knn最近友邻的查找过程。一般在每个节点就是一个很简单的比较,所以速度超级快。结果会有loss,不一定是最相似的友邻。</p>
<p>介绍完两个算法的脾气后,得说说我最近几天和他们相处的感觉。knn适用于数据少的时候。RF用于数据多的时候。首先速度上来说knn就是这样的,另外就是,数据少的时候,友邻质量对你来说特别重要,你都没几个朋友了还都是帮土匪的话那你也就完了。RF作为一种boosting方法,本身就是个statistic概念很强的东西,通过对每个树加随机因子(数据上的或者特征上的随机,也有别的随机方式,譬如增加噪音什么的)让每棵看着差不多,用起来不一样:decorrelation。 这样通过增加variance的方法让结果更好点。但是如果数据少的话,就会适得其反啦。类似的,数据多的时候,knn拿到的优质友邻都很类似,那其实结果就是拿到了更少的友邻,换个角度看的话,你在射交网络天天关注的就几个人,这几个人还都是吃喝拉撒在一个地方的近亲,那他们给的信息大部分都差不多的,这时候你需要weak tie的友邻啦(好吧,我又扯到射交网络了,sorry)。而这个knn的问题rf就能弥补。这里你要说了,你不是说友邻质量很重要么?对,那是在数据少的时候,因为少,从概率上讲友邻的他们之间相似程度不会很高。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[c++里使用octave]]></title>
<link href="http://kkx.github.com/blog/2012/10/19/c-plus-plus-li-shi-yong-octave/"/>
<updated>2012-10-19T22:17:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/10/19/c-plus-plus-li-shi-yong-octave</id>
<content type="html"><![CDATA[<p>蛋疼无极限,想在c++ 里跑octave代码,找到了两个帖子,拼拼凑凑终于可以跑了!
<a href="https://mailman.cae.wisc.edu/pipermail/help-octave/2009-April/034429.html">帖子1</a> 还是邮件列表里的私藏。
照着上面的代码跑一下会发现没有装octave的头文件</p>
<pre><code> Hi,
I tried to find information on how to call an octave .m file from a
C++ program. There is some information around, but most of it is no
longer up-to-date, it was not trivial to get it working. In this post
I want to explain how to do it, maybe it is useful to somebody.
I have a file called exampleOctaveFunction.m which looks like this:
-------------------------------------
function [resultScalar, resultString, resultMatrix] =
exampleOctaveFunction (inScalar, inString, inMatrix)
resultScalar = (inScalar * pi);
resultString = strcat ('Good morning Mr. ', inString);
resultMatrix = (inMatrix + 1);
endfunction
-------------------------------------
I have a file called how-to-call-octave.cpp which is this:
-------------------------------------
#include <octave/oct.h>
#include <octave/octave.h>
#include <octave/parse.h>
#include <octave/toplev.h> /* do_octave_atexit */
int main (const int argc, char ** argv)
{
const char * argvv [] = {"" /* name of program, not relevant */, "--silent"};
octave_main (2, (char **) argvv, true /* embedded */);
octave_value_list functionArguments;
functionArguments (0) = 2;
functionArguments (1) = "D. Humble";
Matrix inMatrix (2, 3);
inMatrix (0, 0) = 10;
inMatrix (0, 1) = 9;
inMatrix (0, 2) = 8;
inMatrix (1, 0) = 7;
inMatrix (1, 1) = 6;
functionArguments (2) = inMatrix;
const octave_value_list result = feval ("exampleOctaveFunction",
functionArguments, 1);
std::cout << "resultScalar is " << result (0).scalar_value () << std::endl;
std::cout << "resultString is " << result (1).string_value () << std::endl;
std::cout << "resultMatrix is\n" << result (2).matrix_value ();
do_octave_atexit ();
}
-------------------------------------
And a little readme file, called readme.sh, that explains how to
compile and run this simple example:
-------------------------------------
#! /bin/bash
#
# Make sure you have octave installed. On my system, Ubuntu 8.10, I installed
# the packages octave3.0 and octave3.0-headers.
#
# To compile, type
#
make how-to-call-octave.o
#
# which, on my system, is equivalent to
#
# g++ -c -o how-to-call-octave.o how-to-call-octave.cpp
#
# To link, type
#
g++ -L /usr/lib/octave-3.0.1/ -l octinterp -o how-to-call-octave
how-to-call-octave.o
#
# Adjust the directory in the -L directive according to the configuration of
# your machine.
#
# To run, type
#
./how-to-call-octave
#
# It should output something like
#
# resultScalar is 6.28319
# resultString is Good morning Mr. D. Humble
# resultMatrix is
# 11 10 9
# 8 7 1
#
# Hope this is of help to somebody.
#
-------------------------------------
You can also execute the readme.sh file and it will compile and run for you.
Good luck, Arjan.
</code></pre>
<p>apt-get 之后发现还是跑不起来, 在<a href="http://octave.1599824.n4.nabble.com/call-octave-from-C-linker-error-td3987581.html">帖子2</a>里找到了解决方法</p>
<pre><code>I started out trying to run the octave example as a c++ file(i.e. no Qt) and received the same errors. The problem is you have to tell the linker where the libs are located. As root, run "ldconfig /path/to/octave/libs". This is not permanent because the next time ldconfig is run it will not find the octave libs.
To make sure the cache gets updated every time ldconfig is run (Ubuntu is a Debian derivative so I'm assuming the procedure is the same) do this:
sudo -i
cd /etc/ld.so.conf.d
touch octave.conf
vi (your fav. editor) octave.conf
add a line: /path/to/octavelibs
save file
run ldconfig
</code></pre>
<p>设置后就能跑啦。最近的博客越来越水了。我又要烂掉了。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[用opencv检测物体中心点]]></title>
<link href="http://kkx.github.com/blog/2012/10/09/yong-opencvjian-ce-wu-ti-zhong-xin-dian/"/>
<updated>2012-10-09T12:35:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/10/09/yong-opencvjian-ce-wu-ti-zhong-xin-dian</id>
<content type="html"><![CDATA[<p>图片中的物体探测一直是比较热的课题,通过物体的检测可以对图片自动生成标签以及分类,可以很好的用在图片检索的领域。在做的毕业设计(也是论文)就涉及到这方面的。今天看一篇叫<a href="http://www.robots.ox.ac.uk/~vilem/cvpr2009.pdf">Class-Specific Hough Forests for Object Detection</a>的state of the art,发现原来导师叫我用到的方法和这个差不多,只是增加了某些小trick用于对特定的数据集优化。</p>
<p>Hough Forests。 其实就是一个random forests 也就是随机森林,每条数据是一小块图片的description(patch) 树的每个节点对数据的分割通过information gain,努力把相同类的patch和相似的patch分到同一个枝头上去。每次通过数据集,随机采集部分features用于分割,每个这些features的分割点生成一个pool。然后对每个feature的分割点做一个信息量的检测,就像一个标准的random forests一样, 这里信息量的计算通过一下程式(这么说比较台湾腔 夯):
$$
Entropy({c_i}) = -c\cdot \log c -(1-c)\cdot \log(1-c)
$$
这里c就是patch是属于物体c的概率,1-c就是negative cases。o
简单吧,其实在原来的论文里还有一个东西,就是entropy只是衡量split的优差的其中一个feature,还有一个feature是offset也就是patch到中心点的距离,通过增加这个衡量点,相似距离的patch会在一起哦。测试的结果是,加上这个feature,precision会有很好的提高。
怎么计算这个feature,其实很简单,用Root mean square(RMS)把split后的patch到中心点的距离计算下,split之后小的RMS更优。到达叶子的数据,每个patch会有一次投票的机会给出中心点的位置。 </p>
<p>训练之后,每个测试图片会被分成不同的patch,通过hough forests 到达叶子的patch生成不同中心点的位置(根据在那叶子里的中心点patch). 最后,通过投票,某个物体的中心点的票数会很高,就这样我们就能找到一个物体的中心点了。 如果一个patch是不是物体的一部分,生成的中心点是错误的,但是中心点的位置其实是随机的,也就是说如果有很多很多非物体patch生成的map是没有一个明显的中心点的。而如果有很多物体中某部位的patch,生成出来的中心点是有一个很大的偏向性的,往往会往某一个中心聚拢。</p>
<p>这里有一个问题就是怎么计算一个patch到中心点的位置。首先训练数据一定得是每张图片只有一个物体。每个patch计算自己中心到物体的中心并保存x轴和y轴的移动。</p>
<p>道理很简单,给我的代码。
opencv这个库让我又爱又恨啊,很好用,很多很复杂的算法只要一行代码就ok了,但是python binding里有很多bug。而且很多错误你不一行行代码拆下来是不会看到的。。。
首先,不想使用论文里的hough forest,因为感觉上在opencv里你得自己hack下,不值得。第二貌似没找到patch的descriptor,所以在自己的实验里,只用到了特征点的descriptors,像sift和surf。使用这个的有点就是,可以做到orientation insensitive和scale insensitive。 其实,论文里的方法是没有能做到scale insensitive的,因为距离是个relative的东西,论文里,通过使用不同的scale进行测试,获得最优的结果作为最终结果。 而view insensitive更没有解决方法,按他们的方法,只有通过增加不同view的训练数据来弥补吧。</p>
<p>但是!导师教给我的方法能避免这个问题。首先sift和surf本身是view insensitive的,而在计算距离的时候通过对特征点本身的大小对距离做个归一化,这样就能让系统获得对不同大小的物体的识别能力。
另外一个trick就是sift之类的特征点自己会带有gradient orientation,也就是梯度方向,这个信息是在计算距离的时候必须使用到的。因为相同descriptor的特征点可能会有不同的梯度方向,如果你只使用了图片原始的x和y轴保存距离的话,预测的中心点会偏离本来的实际位置。下面的图片就是个case:
<img class="center" src="http://kkx.github.com/images/circle.png" width="680" height="420" title="梯度方向" /></p>
<p>我有一个圆圈,底部是白色的,如果是sift当特征点的检测的话,每个圆圈边上的点的特征值是一样的,而他们的梯度方向可能会不一样,如果不使用梯度方向的话,a作为训练的数据,而比作为测试数据,中心点预测的结果会在圆圈的外面(细线部分)。
如果要避免以上问题,只有通过<a href="http://en.wikipedia.org/wiki/Rotation_matrix">rotation matrix</a> 让每个特征点更具自己的梯度方向生成自己的x和y轴。 并且每个特征点的相对于中心点的距离要在新的x和y轴上进行计算。</p>
<ul>
<li>
<script type="math/tex; mode=display"> x_{new} = x \cdot cos(angle) - y \cdot sin(angle) </script>
</li>
<li>
<script type="math/tex; mode=display"> y_{new} = x \cdot sin(angle) + y \cdot cos(angle) </script>
</li>
</ul>
<p>如果需要从新轴返回到原始的x y轴只需要把angle乘以-1就可以了, 上面的angle是以radian为单位的。</p>
<p><img class="center" src="http://kkx.github.com/images/circle2.png" width="680" height="420" title="rotation matrix" />
假设上图是更具特征点的梯度方向做的x和y轴的转换,转换之后,可以发现点离圆圈的中心的距离不变,但是在x轴和y轴的移动发生了变化。</p>
<p>下面是测试方法的代码,包括训练和测试。数据集的话是使用<a href="http://cogcomp.cs.illinois.edu/Data/Car/">ucic</a>里的汽车数据,训练集里有positive cases 和negative cases,这里我只用到了positive cases。也没有是用到hough forest做训练,只用到了最近友邻。 每次选择最相近的友邻做出投票。在训练数据里,所有的车子中心点都在图的当中,因为每张图片里的车子几乎撑满整张图,而所有图片的尺寸都是40x100,在训练数据里,有两部分,一部分是没有scale变化的车子数据库,而另外一部分里车子的大小会有变化,用于测试方法对于scale变化的鲁棒性。 </p>
<p>下面是python代码</p>
<pre><code>import cv2
import math
import sys
import os
import numpy as np
kernel=np.array([[0,0,0,0,0,0,0,0.0002,0.0009,0.0014,0.0016,0.0014,0.0009,0.0002,0,0,0,0,0,0,0],[0,0,0,0,0,0.0005,0.0021,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0021,0.0005,0,0,0,0,0],[0,0,0,0.0000,0.0016,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0016,0.0000,0,0,0],[0,0,0.0000,0.0020,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0020,0.0000,0,0],[0,0,0.0016,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0016,0,0],[0,0.0005,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0005,0],[0,0.0021,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0021,0],[0.0002,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0002],[0.0009,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0009],[0.0014,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0014,],[0.0016,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0016],[0.0014,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0014],[0.0009,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0009],[0.0002,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0002],[0,0.0021,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0021,0],[0,0.0005,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0005,0,],[0,0,0.0016,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0016,0,0],[0,0,0.0000,0.0020,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0020,0.0000,0,0],[0,0,0,0.0000,0.0016,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0016,0.0000,0,0,0],[0,0,0,0,0,0.0005,0.0021,0.0031,0.0032,0.0032,0.0032,0.0032,0.0032,0.0031,0.0021,0.0005,0,0,0,0,0],[0,0,0,0,0,0,0,0.0002,0.0009,0.0014,0.0016,0.0014,0.0009,0.0002,0,0,0,0,0,0,0]])
FLANN_INDEX_KDTREE = 1 # bug: flann enums are missing
AUTOTUNED = 255
flann_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 4)
def get_key_points_from_img(img):
if isinstance(img,str):
img = cv2.imread(img)
im = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
surf = cv2.SURF(1000, 4, 2, True)
keypoints, descriptors = surf.detect(im, None, False)
l_blue = cv2.cv.RGB(200, 200, 250)
descriptors.shape = (-1, surf.descriptorSize())
# y x
center = (im.shape[0]/2,im.shape[1]/2)
attributes = []
for i in range(len(keypoints)):
kp = keypoints[i]
# (x y)
scale = kp.size
#cv2.circle(im, (int(kp.pt[0]), int(kp.pt[1])), int(kp.size/2), l_blue, 2, cv2.CV_AA)
gradient = math.radians(kp.angle)
#calculate the distances x and y in the new axis(rotated by the gradient)
d_x = center[1]-kp.pt[0]
d_y = center[0]-kp.pt[1]
tx = math.cos(gradient) * d_x - math.sin(gradient) * d_y
tx /= scale
ty = math.sin(gradient) * d_x + math.cos(gradient) * d_y
ty /= scale
# generate data for further experiments
attributes.append((kp.pt[0], kp.pt[1], scale, gradient, ty, tx, 0, 0, 0, 0, 1))
return (descriptors,attributes)
if __name__ == "__main__":
dir_path = "./CarData/TrainImages/"
files = os.listdir(dir_path)
#build train dataset
train_descriptors = []
train_attributes = []
for f in files:
if f.startswith("pos"):
(descriptors, attributes) = get_key_points_from_img(dir_path+f)
train_descriptors.extend(descriptors)
train_attributes.extend(attributes)
print len(train_descriptors)
train_descriptors = np.array(train_descriptors)
#build pnn model
flann_index = cv2.flann_Index(train_descriptors, flann_params)
dir_path = "./CarData/TrainImages/"
dir_path = "./CarData/TestImages/"
dir_path = "./CarData/TestImages_Scale/"
files = os.listdir(dir_path)
count1 = 0
count2 = 0
for f in files:
img = cv2.imread(dir_path+f)
(descriptors, attributes) = get_key_points_from_img(img)
im = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mapa = np.zeros(im.shape, dtype=float)
indexes, dist = flann_index.knnSearch(descriptors, 15, params={})
for i in range(len(descriptors)):
for j in range(len(indexes[i])):
scale = attributes[i][2]
gradient = attributes[i][3]
d_y = train_attributes[indexes[i][j]][4]*scale
d_x = train_attributes[indexes[i][j]][5]*scale
tx = math.cos(-gradient) * d_x - math.sin(-gradient) * d_y
ty = math.sin(-gradient) * d_x + math.cos(-gradient) * d_y
predicted_center_x = int(round(attributes[i][0]+tx))
predicted_center_y = int(round(attributes[i][1]+ty))
if (predicted_center_x>=0) and (predicted_center_y>=0) and (predicted_center_x<mapa.shape[1]) and (predicted_center_y<mapa.shape[0]):
mapa[predicted_center_y][predicted_center_x] += 1
count1 += 1
else:
count2 += 1
#apply a matlab disk filter to the output map for visualization
mapa2 = cv2.filter2D(mapa,-1,kernel)*5
cv2.imshow("original",im)
cv2.imshow("image", mapa2)
cv2.waitKey()
</code></pre>
<p>一大坨kernel变量,其实就是matlab的disk filter: fspecial(‘disk’, 20) opencv里就没有合适的filter可以用来平滑图片的!。 这里使用到了flann用来做最近友邻的模型,由于数据不是特别大,所以这里也没什么优化。在训练的时候,会把每个特征点距离到中心点的距离计算一下,计算完会根据特征点的大小把距离缩放一下,而当在测试的时候也会根据每个特征点的大小把距离放大。这样就能达到scale insensitive的目的了。 每个特征点会根据友邻做投票,投票的结果会映射到一个和图片一样大小的map上面。 通过filter2d过滤之后,会出现一大坨模糊的东西,哪里最亮哪里的投票数就最多,是中心点的可能性也最大。</p>
<p>下面就是一些实验图片的结果。
<img class="center" src="http://kkx.github.com/images/oct_9_t_1.png" width="680" height="320" title="梯度方向" />
<img class="center" src="http://kkx.github.com/images/oct_9_t_2.png" width="680" height="320" title="梯度方向" />
<img class="center" src="http://kkx.github.com/images/oct_9_t_3.png" width="680" height="320" title="梯度方向" />
<img class="center" src="http://kkx.github.com/images/oct_9_t_4.png" width="680" height="320" title="梯度方向" />
<img class="center" src="http://kkx.github.com/images/oct_9_t_5.png" width="680" height="320" title="梯度方向" />
<img class="center" src="http://kkx.github.com/images/oct_9_t_6.png" width="680" height="320" title="梯度方向" /></p>
<p>貌似结果很好的,但是其实不是的,因为这里没用到false cases,会出现很多false positive,而且,由于是使用sift detector, 本身控制不了哪些特征点是需要的哪些是不需要的,在测试后的时候,如果测试图片的某个非物体部位的特征点很多的话(细节比较多),会产生很多假票, 像最后一张图的两根柱子。在下次的试验力,一定得吧那些false cases包含到训练库里,当测试数据碰到的一个false case的票,直接把权值归0,这样的话应该能对结果有很大的改观。还有就是这里,每张投票的权值都是1,可以根据友邻的距离设置投票的权值,近的投票全职大,这样结果应该会好一些。 </p>
<p>opencv用起来真的没有matlab舒服,就连一个类似matlab的imregionalmax函数都没有,这个函数可以计算local maximum,这样的话,选取峰值最高的几个点作为预测结果会有很理想的结果。所以这套代码在这里也不能用来获得和真实中心点做比较。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[FAST APPROXIMATE NEAREST NEIGHBORS(FLANN)]]></title>
<link href="http://kkx.github.com/blog/2012/09/24/fast-approximate-nearest-neighbors-flann/"/>
<updated>2012-09-24T20:04:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/24/fast-approximate-nearest-neighbors-flann</id>
<content type="html"><![CDATA[<p>最近需要使用最近友邻对高纬度的数据进行查询。在实验的过程中发现相对于高纬度的数据,kd树的查找速度其实没有线性查找来的快的,但是在大数据面前,线性查找的开销是很大的。我在网上找了一些相关资料,最后发现有个叫flann的库很不错,这个库可以让根据用户设置的查找准确性确定性和用户提供的数据自动生成一个合理高效的数据结构,让每次搜索的速度比线性查找快几个数量级,同时丢失一些准确度。程序上自动的对参数和数据结构的设置,让使用者不用担心该使用什么样的方法来解决问题。</p>
<p>网上有各类优化的最近友邻查找方法的论文,很多时候针对每个问题,用的方法不一样能产生不同的优化。通过论文实验报告,有2个方法是在比较不错的:</p>
<ul>
<li>Randomized kd-tree algorithm: 不同于普通的kd-tree,这个方法是通过生成一组(大于等于1)的kd树来储存数据。该方法是通过在最之前的D次随机数据分割从而获得方差最大的分割(D=5)。当搜索的时候所有树会根据和数据的距离进行排列,距离近的树先被排查,只会查找固定数目的leaf。</li>
<li>Hierarchical k-means tree algorithm: 这个算法就是每次把数据进行k组分割(k-means) 每分割完的小组继续进行k组分割,直至每组数据小于K。这里随机部分是:首先查询数据会在树里过一遍,到达叶子的时候会生成一个优先队列,在队列里,查询数据到达最有叶子里的路径里的所有节点里没有访问过的branch, 队列的排列方式根据他们的中心位置和查询数据的距离从小到达排列。每次会从优先队列里提取一个节点从那里一直到树叶进行查找最近友邻,并同时在优先队列里插入新的未访问branch。查找的次数根据用户的提供的准确率百分比。</li>
</ul>
<p>最有参数的自动选择没有怎么整明白(刻意关系),貌似是使用10%的数据做一个检测,参数的选择会在固定的几个之中进行选择。用户使用的时候往往需要提供一些使用场景的参数,比如: 查找速度,建表速度,内存使用速度,更具这几个参数,他会自动给你找到最优的方法和方法参数。只提供使用场景的这些参数更直观的让不是怎么了解算法本身的人能够用最合适的算法做最合适的事了。具体cost计算方程在论文的程式1中。</p>
<p>使用的感脚良好,很简单,速度也靠谱。有python matlab 等的binding,opencv里还自带了这个库!在mac上安装matlab上安装的时候碰到几个小问题。安装步骤可以参考<a href="http://www.cs.uky.edu/~jacobs/tips/flann_matlab.html">http://www.cs.uky.edu/~jacobs/tips/flann_matlab.html</a>。当中需要装一个patch让matlab里的mex可以能编译。地址在<a href="http://www.mathworks.es/support/solutions/en/data/1-FR6LXJ/">这里</a>装完之后更新下配置文件就能在matlab上编译c++文件了!</p>
<p>这个库的使用也是很容易的: </p>
<pre><code>[index, search_params, speedup] = flann_build_index(mat_train_data(:,:), struct('algorithm','autotuned', 'target_precision',0.95, 'build_weight',0.01, 'memory_weight',0))
%这里表示无所谓内存开销,建立索引开销重视一般。
%搜索
[result, ndists] = flann_search(index, mat_train_data(:,1), 5, search_params);
%保存
flann_save_index(index,'train.idx')
</code></pre>
<p>不过还是有很蛋疼的地方,比如,有时候你得save一下再重新load之后才能搜索, 还有就是里面数据和matlab的数据表现形式不一样啊,让我废了好久才弄明白,一般数据分析总是会把数据一行行保存,每个row是一条数据。但是这里他是反人类的反过来了!使用的时候要非常注意。</p>
<p>参考:</p>
<ul>
<li>1 FAST APPROXIMATE NEAREST NEIGHBORS WITH AUTOMATIC ALGORITHM CONFIGURATION</li>
<li>2 http://www.cs.uky.edu/~jacobs/tips/flann_matlab.html</li>
<li>3 http://www.cs.ubc.ca/~mariusm/index.php/FLANN/FLANN</li>
</ul>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[马赛克图片拼接]]></title>
<link href="http://kkx.github.com/blog/2012/09/16/ma-sai-ke-tu-pian-pin-jie/"/>
<updated>2012-09-16T13:21:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/16/ma-sai-ke-tu-pian-pin-jie</id>
<content type="html"><![CDATA[<p>以前就想做一个马赛克合成的图片了,最近不务正业,就写了一个
方法很简单:</p>
<ul>
<li>抓取一组图片的并缩放到统一大小</li>
<li>计算每张图片的rgb值的中值</li>
<li>所有的图片中值生成kd-tree</li>
<li>加载目标图片,并缩放到想要的结果图片的大小,然后创建结果图片(np.zeros)</li>
<li>每一小格结果图片的大小和抓取的小图大小一样,计算目标图片每小格的rgb 中值</li>
<li>查找最想进的小图,并用小图的rgb值填充到结果图片里</li>
<li>生成结果图片</li>
</ul>
<p>马赛克图片生成代码:</p>
<pre><code>import Image
import os
import numpy as np
from scipy import spatial
#calculate a mean rgb of an image
def meanRGB(img):
rgb = np.array(img.getdata())
mean_r = np.mean(rgb[:,0])
mean_g = np.mean(rgb[:,1])
mean_b = np.mean(rgb[:,2])
return [mean_r, mean_g, mean_b]
if __name__ == "__main__":
#grid size
mosaic_img_size = (20, 20)
#size of target image to generated
target_img_size = (1000, 1000)
target_img = Image.open("./VanGogh-starry_night_ballance1.jpg")
target_img = target_img.resize(target_img_size)
#grid images directory
directory = "./icons/"
listing = os.listdir(directory)
img_list = []
color_list = []
#load images and calculate mean rgb
for infile in listing:
if infile.endswith("jpg"):
img = Image.open(directory+infile)
img = img.resize(mosaic_img_size, Image.ANTIALIAS)
img_list.append(img)
color_list.append(meanRGB(img))
#create kdt for future pnn query
data = np.array(color_list)
tree = spatial.KDTree(data, leafsize=5)
count_x = 0
count_y = 0
new_image = np.zeros((target_img_size[0],target_img_size[1],3), dtype=np.uint8)
target_img_data = np.reshape(np.array(target_img.getdata()), (target_img_size[0],target_img_size[1],3))
length = mosaic_img_size[0]*mosaic_img_size[1]
#calculate mean rgb of each grid in the target image, search a nearest neighbor and fill the new image with
#the rgb values of the grid image
while count_x+mosaic_img_size[0] < target_img_size[0]:
count_y = 0
while count_y+mosaic_img_size[1] < target_img_size[1]:
mean_r = np.mean(target_img_data[count_x:count_x+mosaic_img_size[0],count_y:count_y+mosaic_img_size[1],0])
mean_g = np.mean(target_img_data[count_x:count_x+mosaic_img_size[0],count_y:count_y+mosaic_img_size[1],1])
mean_b = np.mean(target_img_data[count_x:count_x+mosaic_img_size[0],count_y:count_y+mosaic_img_size[1],2])
img_index = tree.query(np.array([mean_r,mean_g,mean_b]))[1]
new_image[count_x:count_x+mosaic_img_size[0],count_y:count_y+mosaic_img_size[1],:] = np.reshape(np.array(img_list[img_index].getdata()), (mosaic_img_size[0],mosaic_img_size[1],3))
count_y += mosaic_img_size[1]
count_x += mosaic_img_size[0]
#image created
im = Image.fromarray(new_image)
im.show()
im.save("./1VanGogh-starry_night_ballance1.jpg")
</code></pre>
<p>里面有些小bug,但不影响使用,譬如最右最下有一圈黑色的,是代码中某个逻辑没写好,没有填充进去,但是不管它了- -(貌似把小于号改成小于等于就可以了)</p>
<p>原图:
<img class="center" src="http://kkx.github.com/images/VanGogh-starry_night_ballance1.jpg" width="600" height="600" title="原图" />
效果:
<img class="center" src="http://kkx.github.com/images/1VanGogh-starry_night_ballance1.jpg" width="600" height="600" title="效果" />
768的向量空间长度,图片量一定要大一些,要不然的结果会是,颜色很单一,拼出来的马赛克效果不好。自己的友邻太少了,可能结果会不理想,所以这里我抓了豆瓣活动上的豆友,570多位。。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[高度相似图片检测: 3 Perceptual Hash(phash) 图片指纹]]></title>
<link href="http://kkx.github.com/blog/2012/09/10/gao-du-xiang-si-tu-pian-jian-ce-3perceptual-hash-phash-tu-pian-zhi-wen/"/>
<updated>2012-09-10T17:03:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/10/gao-du-xiang-si-tu-pian-jian-ce-3perceptual-hash-phash-tu-pian-zhi-wen</id>
<content type="html"><![CDATA[<p>最后一个要介绍的图片指纹生成方法: pHash, 这里是pHash算法简要:</p>
<ul>
<li>图片指纹灰值化</li>
<li>图片缩放到32x32</li>
<li>计算机视觉每个图片的Discrete Cosine Transform (DCT, type II)。</li>
<li>只保留最左上部的8x8的DCT系数(有32x32大),这样保存了频率最低的那部分(图片信息的大部分)</li>
<li>计算中值</li>
<li>生成64维2维码,0表示系数小于中值,反之为1.</li>
</ul>
<p>说到DCT,其实就想傅里叶转换一样的,把信号转换成很多不同频率和振幅的正玄曲线相加的结果。但是DCT只是用cosine函数。这样的话图片高频率部分会给擦去,只保留低频率部分。因为在给图片操作的时候,往往低频率的DCT系数会给保留,而且大部分图片的信息会给保留在这些低频率DCT系数里。(JPEG压缩也用这个方法来对图片进行压缩)。
DCT 会生成8x8的系数表,那最左上方的表示最低频率的元素,也是最重要的,越往右下的表示相对频率稍高的元素(好多信号出来的东西啊,头好大)
反正到最后能生成一个图片指纹,从别的作者的实验结果来看,效果是相对较好的。在这个网站上<a href="http://phash.org">phash.org</a>能下到开源的代码,在网站的demo上可以可以试试看计算图片的相似性,通过测试,只有DCT比较靠谱,另外两个的结果非常糟糕。相同的,这个算法的信息量为2^64,冲撞率应该不高,吧?</p>
<p><a href="http://phash.org">phash.org</a>网站上是用c/c++写的代码,没有python的binding,在<a href="https://github.com/polachok/py-phash/">https://github.com/polachok/py-phash/</a>可以找到一个简陋的python binding,里面有我们需要的DCT的计算方法,调用一个函数就能直接获得指纹数值。测试了几下,效果还不错。不过还是需要很大量的图片用语测试false positive的概率。
在github上能看到作者写的使用方法说明,我这里具体的代码如下:</p>
<pre><code>
import pHash
import sys
if __name__ == "__main__":
hash1 = pHash.imagehash(sys.argv[1])
hash2 = pHash.imagehash(sys.argv[2])
print 'Hamming distance: %d (%08x / %08x)' % ( pHash.hamming_distance( hash1, hash2 ), hash1, hash2 )
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[高度相似图片检测: 2图片缩小指纹]]></title>
<link href="http://kkx.github.com/blog/2012/09/09/gao-du-xiang-si-tu-pian-jian-ce-2tu-pian-suo-xiao-zhi-wen/"/>
<updated>2012-09-09T00:09:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/09/gao-du-xiang-si-tu-pian-jian-ce-2tu-pian-suo-xiao-zhi-wen</id>
<content type="html"><![CDATA[<p>图片缩小之后其实保留个部分图片的信息,可以用来当做图片的指纹,在查找相关资料的时候发现了一个很不错的<a href="http://hackerlabs.org/blog/2012/07/30/organizing-photos-with-duplicate-and-similarity-checking/">博客</a>
在这个博客里,几乎所有的和图片相似性计算的算法都在了(除了基于语义的)。超级不错。里面的很多算法的实现是用pearl的,而本身抱着学习的态度我决定重新用python对其中的某些算法实现一遍。</p>
<p>这里是图片缩小指纹的算法:</p>
<ul>
<li>缩放到160x160的图片</li>
<li>灰值化</li>
<li>模糊图片,把噪音去除掉</li>
<li>equalize,让图片反差更大</li>
<li>计算图片平均值</li>
<li>缩放到16x16的图,并且降到2维,更具图片pixel值:大于平均值的为1,反之为0</li>
</ul>
<p>这样就生成了图片的指纹,信息总量有2^256, 哈希冲撞应该不大。
计算的图片相似度的时候直接使用xor。公式可以是这样: 1 - xor(p1,p2)/256</p>
<p>具体python实现代码如下:</p>
<pre><code>from PIL import Image
import sys
import ImageFilter
import ImageOps
import numpy as np
def normalize(img, size = (160, 160)):
return ImageOps.equalize(img.resize(size).convert('L').filter(ImageFilter.BLUR))
def calculate_fingerprint(img):
img = normalize(img)
mean_value = np.mean(np.array(img.getdata()))
img = img.resize((16,16))
return np.array(img.getdata())>mean_value
def calculate_simility_by_path(img_path1, img_path2):
img1 = Image.open(img_path1)
img2 = Image.open(img_path2)
fingerprint1 = calculate_fingerprint(img1)
fingerprint2 = calculate_fingerprint(img2)
return 1 - (0.0+np.sum(np.logical_xor(fingerprint1, fingerprint2)))/fingerprint1.size
if __name__ == "__main__":
print calculate_simility_by_path(sys.argv[1],sys.argv[2])
</code></pre>
<p>更好的implementation可以在这个<a href="http://www.ostertag.name/HowTo/findimagedupes.pl">链接</a>找到,虽然做法上不一样,但是原理应该都差不多。在代码的注释里,能看到这个类算法的优点和缺点,如果图片相似度较高的话,识别起来没什么问题。最大的缺点是,很多false positive,如果图片是一张大海的话 会出现一下情况</p>
<pre><code>1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
</code></pre>
<p>作者还说到对于大规模数据的话,性能会降低很多。但是貌似这个能用bloomfilter解决,现在先不管他</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[高度相似图片检测: 1颜色直方图差]]></title>
<link href="http://kkx.github.com/blog/2012/09/08/gao-du-xiang-si-tu-pian-jian-ce-1yan-se-zhi-fang-tu-chai/"/>
<updated>2012-09-08T15:30:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/08/gao-du-xiang-si-tu-pian-jian-ce-1yan-se-zhi-fang-tu-chai</id>
<content type="html"><![CDATA[<p>对于颜色直方图差的比对能很好的检测出图片之间的色差变化,那对于两张环境,光线,角度长不错的图片,能很好的检测出他们的相似度。
这是一个很老的方法,简单粗暴,非常使用。原理在这篇<a href="http://blog.csdn.net/lanphaday/article/details/2325027">博客</a>里很清楚的讲解了。 算法很简单:</p>
<ul>
<li>把两张图片缩放到统一的大小,这里可以是256x256, 并且统一成rgb模式。</li>
<li>统计每张图片的每个颜色的出现次数。那这里因为是rgb模式,有3*256=768 种颜色,生成直方图</li>
<li>对两张图片的直方图进行比对。如果统计结果一致的话,相似度+1</li>
</ul>
<p>这里有个问题就是对空间信息的丢失,原作者通过把图片分割成16个小方格来解决这个问题。(split_image函数)</p>
<p>具体代码如下:</p>
<pre><code>from PIL import Image
import sys
def normalize(img, size = (256, 256)):
return img.resize(size).convert('RGB')
def sim_hist(hist1, hist2):
assert len(hist1) == len(hist2)
return sum(1 - (0 if hist1 == hist2 else float(abs(hist1 - hist2))/max(hist1, hist2)) for hist1, hist2 in zip(hist1, hist2))/len(hist1)
def calc_similar_by_path(path_img1, path_img2):
li = normalize(Image.open(path_img1))
ri = normalize(Image.open(path_img2))
return sum(sim_hist(l.histogram(), r.histogram()) for l, r in zip(split_image(li), split_image(ri))) / 16.0
def split_image(img, part_size = (64, 64)):
w, h = img.size
pw, ph = part_size
assert w % pw == h % ph == 0
return [img.crop((i, j, i+pw, j+ph)).copy() \
for i in xrange(0, w, pw) \
for j in xrange(0, h, ph)]
if __name__ == "__main__":
print calc_similar_by_path(sys.argv[1],sys.argv[2])
</code></pre>
<p>这里的fingerprint就是每个图片的直方图。 这个指纹的信息量的话应该挺大的(至少有2^768) 理论上来讲应该false positive的可能性很小,但是这个结论应该是错的,因为在图片里很多颜色是出现的概率是很小的,大部分颜色都集中于某些值中。</p>
<p>代码和原作者的差不多,实验结果是不错的,很简单,但是这个算法有个致命的问题就是对颜色的过分依赖。对于图片角度的变化的容忍度挺高,但是如果颜色或者图片光线变化稍大就不能很好的检测出相似度了,会把相似的图片判断为不同的。最简单的方法就是弄个灰度图,这方法基于rgb的,就无解了。还有个小问题就是,false positive,我觉得如果两张白底黑字的文字图片,不管字是多么不一样,但是他们的相似度应该很高(在有的应用上,这个结果是很希望的)</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[高度相似图片检测: Introduction]]></title>
<link href="http://kkx.github.com/blog/2012/09/08/gao-du-xiang-si-tu-pian-jian-ce-introduction/"/>
<updated>2012-09-08T14:18:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/09/08/gao-du-xiang-si-tu-pian-jian-ce-introduction</id>
<content type="html"><![CDATA[<p>在db实习了2个多月很开心。老实说,在那里做的项目没有想象中的理想,觉得惭愧啊。前段时间清风老师有和我提起一个敏感图片检测的需求。当时候有试着用SIFT和min-hash实现,但是结果不是很理想。在公司和在家自己弄有很大的区别,时间上的紧迫,很多时候虽然头不会催,但是你看着别人在交东西,自己却没有,压力很大,很多时候就需要move on。结果就是这个项目没有完成, 感脚好对不住清风老师啊!真的不会再爱了!</p>
<p>这几天闲下来,我突然有想好好研究研究这方面的东西了,看了些paper,觉得有很多实现方法,结果有好的有坏的,我准备都试着去实现下,把他们的有点和缺点都记录下来,希望能找到一个很好的解决方案,如果运气好的话。</p>
<p>Near-duplicate image(高相似图片)的检测不比完全一样的图片检测来的简单,后者可以直接用哈希生成像MD5类似的指纹,然后保存每个图片的时候也保存那个指纹,这样在查找的时候只要比对指纹就可以了,这样的话速度上会有很大的提升。</p>
<p>那用什么样的办法能有效的比对图片的相似度呢? 方法有很多。 首先要说的一点是,在比对的时候,速度是非常重要的,所以,一般都是通过指纹(fingerprint)技术把一张图片合理的压缩成一个容量占用很小的方便计算相似度的数据集。 前面说过md5是不能用在这里的,为什么呢?因为一个微小的变化会是两个图片之间的MD5完全不一样。而在这里要做的是:</p>
<ul>
<li>相同图片的指纹要一样</li>
<li>类似图片的指纹也要类似</li>
<li>完全不相同的图片指纹的差别很大</li>
</ul>
<p>做到以上几点,那么图片的相似度的识别就完成了,但是要找到一个函数f做到以上这种方法很难。这也是这里要慢慢探索的。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[tesseract-ocr 中文字符识别速度慢的问题]]></title>
<link href="http://kkx.github.com/blog/2012/07/08/tesseract-ocr-zhong-wen-zi-fu-shi-bie-su-du-man-de-wen-ti/"/>
<updated>2012-07-08T04:01:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/07/08/tesseract-ocr-zhong-wen-zi-fu-shi-bie-su-du-man-de-wen-ti</id>
<content type="html"><![CDATA[<p>在做这个ocr项目的时候,首先想到的是用一些开源的项目,通过比较,只有google对中文的支持度算是有好的,通过测试,它的识别度也还是可以。但是会面临到好几个问题,可以察觉到2个主要问题</p>
<ul>
<li>中文字相比英语字符,识别速度低了好几倍</li>
<li>中文字符有时候会整行出现大面积的错误,而且非常离谱,甚至有乱码的出现。</li>
</ul>
<p>通过这篇<a href="http://static.googleusercontent.com/external_content/untrusted_dlcp/research.google.com/en/us/pubs/archive/35248.pdf">Adapting the Tesseract Open Source OCR Engine for Multilingual OCR</a>的论文里看,这个项目的框架为了能让各种不同语言的文字进行识别,把中文文字识别以一行句子为切分单位,以文字部首为最基本的识别单位(使用了connected components analysis切分所有不连在一起的部首(也可以用项目的图形debug界面http://code.google.com/p/tesseract-ocr/wiki/ViewerDebugging,能看到中文字是给切分开的)。识别之后,通过best first search对中文字符进行匹配。结果就是,文字识别速度较慢,大部分计算的开销用在了匹配的时候。由于用到了best-first 不能保证全局最优解,往往图像稍微有一些噪音就会会出现大面积的错误的情况,一开始的几个字也许和原本字符相似度还可以,句子后半部的字符就不是了。</p>
<p>这个方法完全是按英语的识别方法,通过以英语单词为基本匹配单位,匹配每个字母成为最有可能的单词。这样的一个方法可以让语言的semantic运用到识别了,大大提高的每个单词的识别度。但是这个方法的本身是特别适合语英语,每个单词的长度不是很大,单词和单词之间有一个明显的空格,可以很好的切分。 但是运用到中文字符识别的时候确出现了以下几个问题:</p>
<ul>
<li>以google那些人的看法,中文字符之间空隙是很小的,甚至很多时候是字和字之间是连在一起。其实印刷体的中文字符之间的空隙还是很明显的。</li>
<li>中文句子长度一般都很长,往往匹配的时候需要很多开销</li>
<li>中文识别的时候,我试过,就算把文本字符之间的空隙分的很大,还是会把一个字的之间的部首分开,在匹配的时候不会记住字符之间的空隙。</li>
</ul>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[生成中文字图片]]></title>
<link href="http://kkx.github.com/blog/2012/06/28/sheng-cheng-zhong-wen-zi-tu-pian/"/>
<updated>2012-06-28T17:17:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/06/28/sheng-cheng-zhong-wen-zi-tu-pian</id>
<content type="html"><![CDATA[<p>最近在做一个中文ocr,感觉是个不可完成任务。不管怎么样还是得试试。
首先要做的就是获得训练数据。中文的数据不比英文,量大而且没有地方下载,唯一能做的就是自己生成,在网上找了写函数自己改改就成了下面的脚本了</p>
<p>首先是一个中文字符集编码的生成函数:</p>
<pre><code>
def generate_ch():
#Function which generate all chinese gb2312 characters
body = random.randint(0xA, 0xA)
tail = random.randint(0, 0xF)
chars = []
heads = range(0xB0, 0xF8)
bodys = range(0xA, 0xF+1)
tails = range(0, 0xF+1)
for head in heads:
for body in bodys:
for tail in tails:
val = ( head << 0x8 ) | (body << 0x4 ) | tail
#print "%x"%head,"%x"%body,"%x"%tail,"%x"%val
str = "%x" % val
if str.endswith("ff") or str.endswith("a0"):
continue
#special cases no characters with these codecs
if str == "d7fa" or str == "d7fb" or str == "d7fc" or str == "d7fd" or str == "d7fe" or str == "d7ff" :
continue
chars.append(str.decode('hex').decode('gb2312'))
return chars
</code></pre>
<p>如果要生成中文字符图片,有很多办法,这里我用pygame打印每个字符</p>
<pre><code> # -*- coding: utf-8 -*-
import pygame
pygame.init()
font = pygame.font.Font(os.path.join("fonts", "simfang.ttf"), 64)
rtext = font.render("".join("b1a1".decode('hex').decode('gb2312')), True, (0, 0, 0), (255, 255, 255))
pygame.image.save(rtext,"t.jpg" )
</code></pre>
<p>然后中文字符图片就出来了
<img class="center" src="http://kkx.github.com/images/character.jpg" width="300" height="300" title="原图" /></p>
<p>要打印字符,需要至少一个ttf,也就是字体的文件。这里是用的simfang.ttf</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Gabor filters对中文字图片的特征提取]]></title>
<link href="http://kkx.github.com/blog/2012/06/28/gabor-filtersdui-zhong-wen-zi-tu-pian-de-te-zheng-ti-qu/"/>
<updated>2012-06-28T16:52:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/06/28/gabor-filtersdui-zhong-wen-zi-tu-pian-de-te-zheng-ti-qu</id>
<content type="html"><![CDATA[<p>中文字ocr是个比较讨厌的事情,主要是因为中文字繁多,光简体字就有6000多个以上,虽然其中大部分简体字是不常用的。对于这么一个这么大的,种类繁多的数据集,在分类的时候真心头疼,看了很多paper后,发现现在的state of the art是基于gabor filters,通过这个过滤器,对一个中文字的不同角度的比划进行过滤,然后在每个subregion里的每个角度的比划进行统计之后生成一个histogram,和bag of words一样,histogram可以用来对每个字进行分类或者用来寻找最近邻。</p>
<p>gabor filters 是个复杂的东西,网上的方程各种各样的,数学不好的小白就很难明白。在网上找了好多现成的函数之后,最后找到一个能用的matlab函数:</p>
<pre><code>
function gb=gabor_fn(rows,cols,sigma,theta,lambda,psi,gamma)
sigma_x = sigma;
sigma_y = sigma/gamma;
% Bounding box
nstds = 3;
xmax = max(abs(nstds*sigma_x*cos(theta)),abs(nstds*sigma_y*sin(theta)));
xmax = ceil(max(1,xmax));
ymax = max(abs(nstds*sigma_x*sin(theta)),abs(nstds*sigma_y*cos(theta)));
ymax = ceil(max(1,ymax));
xmin = -xmax; ymin = -ymax;
[x,y] = meshgrid(xmin:xmax,ymin:ymax);
%[x,y] = meshgrid(-rows/2:rows/2-1,-cols/2:cols/2-1);
% Rotation
x_theta=x*cos(theta)+y*sin(theta);
y_theta=-x*sin(theta)+y*cos(theta);
gb= exp(-.5*(x_theta.^2/sigma_x^2+y_theta.^2/sigma_y^2)).*cos(2*pi/lambda*x_theta+psi);
</code></pre>
<p>rows和cols是图片的大小, sigma是gauss的那个sigma,越大的话周围的pixels影响越大,theta要提取的比划的角度,lambda这个貌似是什么的长度(不明白啊),psi这里没用,gamma生成y_sigma。</p>
<p>通过改变theta这个角度值可以获得不同角度比划的信息。</p>
<pre><code>
I = imread('~/Desktop/11.png')
J = rgb2gray(I);
K = imresize(J,[64,64])
gb=gabor_fn(64,64,1.2,theta,5,0,1)
</code></pre>
<p>这里的theta分别是pi/2, pi/4, pi和 3*pi/4 这个四个值,他们可以分别抓取中文字比划的那8个方向。
下面就是这个函数能产生的效果</p>
<p><img class="center" src="http://kkx.github.com/images/gabor.png" width="100" height="100" title="原图" />
<img class="center" src="http://kkx.github.com/images/gabor45.png" width="100" height="100" title="45º" />
<img class="center" src="http://kkx.github.com/images/gabor90.png" width="100" height="100" title="90º" />
<img class="center" src="http://kkx.github.com/images/gabor135.png" width="100" height="100" title="135º" />
<img class="center" src="http://kkx.github.com/images/gabor180.png" width="100" height="100" title="180º" /></p>
<p>冲上面的图就能看出这个过滤器的威力,过滤图片之后,我们可以对每个图片其中某个的区域的点阵进行统计,这样生成一个histogram,通过histogram可以有效的对每个字进行区分。</p>
<p>在我的试验中,我用了16个16<em>16大小的方格作为第一次层histogram数组,然后在它的上面又叠加了9个16</em>16的数组,总共是一个长(16+9)*4 = 100的数组。这么长的数组以后还的进行pca或者lda什么。实验在进行中。。。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[数据集不平衡问题]]></title>
<link href="http://kkx.github.com/blog/2012/05/30/shu-ju-ji-bu-ping-heng-wen-ti/"/>
<updated>2012-05-30T18:06:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/05/30/shu-ju-ji-bu-ping-heng-wen-ti</id>
<content type="html"><![CDATA[<p>期末的另外一个大作业是对一个数据进行分类和研究。数据集是和保险行业相关的。94%的negative cases和6%的positive cases。 如此不平衡的数据往往你能通过使用最简单的分类器<a href="http://chem-eng.utoronto.ca/~datamining/dmc/zeror.htm">zeroR</a>来获得较高的准确率(94%). 在实际应用中,这样是非常不靠谱的。因为往往那些positive cases的忽略产生的影响是比较大的([FRR]的惩罚)。</p>
<p>同样,直接使用不平衡数据会带来另外一个影响是: 往往分类器模型追求的是高准确率,自然而然的将会给高概率的类影响,结果将会是一个和<a href="http://chem-eng.utoronto.ca/~datamining/dmc/zeror.htm">zeroR</a>一样的分类器。在不同的测试之后,发现这个现象非常明显。</p>
<p>那怎么样可以让分类器”学会一点实在的东西”, 而不是只追求”富贵(高准确率)”,或者两者皆之呢。有一种方法就是通过resampling,人为的让训练数据集的分布更平衡一些,通过这样的方法,训练出来的模型会更靠谱一些。但是同样的,这个模型在实际环境中的预测结果不会是很好的,因为我们改变了分布,不同分布下的同一个模型会导致结果的不同。怎么解决这类问题呢? 通常需要一个calibration,让模型试用于实际环境中的数据分布。在这篇<a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.58.3311&rep=rep1&type=pdf">Dealing with Unknown Priors in Supervised Classification</a>作者解释了怎么通过贝叶斯概率来做这个校准。在试验中,这样的校准对我的实验结果没有什么影响,主要的原因是像下面所谈到的,我的模型的结果是一个概率值,我需要选出概率最高的那些,而这个校准是<a href="http://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>的,对概率排序不产生影响。如果是组合不同的分类器的话,这个办法应该还是有用的。另外我还觉得,就算有组合不同的分类器,如果实际环境中数据分布差值在这么大结果也会一样不理想。</p>
<p>期末的这个作业其实是让我们训练出某个模型,并让模型的结果是一个positive cases的概率值,我们需要在测试数据中,选出10%的数据,而这些数据中的positive cases的比重要尽可能的多。这里我介绍两个办法。</p>
<h2 id="random-forest">Random Forest</h2>
<p>比较正规,完全是跟着老师说的也就是上面说的那些办法来做。 碰到这样的数据集,往往分类器倒不是最重要的部分。很多preprocessing将会是一样的重要。数据拿来首先要自己分析一下哪些attributes是不需要的,一般会用information gain来看一些,某些gain特别高的也不一定是好feature,也许这些feature是些”事后”feature,对预测没有任何帮助。然后呢,会用一些<a href="http://en.wikipedia.org/wiki/Dimension_reduction">dimension reduction</a>的方法比如PCA,LDA,mRMR(这里我用的)对数据进行降维。</p>
<p>降为之后,使用了upsampling 对训练数据集的分布进行更改,upsampling是把少的那部分复制起来,听说结果要比downsampling好。 然后呢,就是不同的测每个模型。最后我的模型是一个cost-sensitive + random forest. </p>
<p>这里是我使用WEKA做分析的流程图:
<img class="center" src="http://kkx.github.com/images/weka.png" width="650" height="850" /></p>
<p>我的最终结果是在10%的数据里,有12.56%的positive cases。下面是precision-recall的结果:
<img class="center" src="http://kkx.github.com/images/precision-recall.png" width="650" height="850" /></p>
<h2 id="svm">SVM</h2>
<p>在做这个作业的事后在resys发过帖子问过,幸运的是有个浙江大学的牛人和我专门讨论了这个问题,当时他给了我一个方法就是通过SVM来做模型,这里的变化就是,通过soft margin,对每个类的给出不同的惩罚值C,这样的话,多数的那类的C会很小,而烧的那类C值会很大。当时一说就觉得非常靠谱,但是项目已经快deadline 也就没事间测试了。</p>
<p>C的方程: </p>
<blockquote>
<script type="math/tex; mode=display">C\sum_{i=1}^l\epsilon_i = C_+\sum_{i\in I_+}\xi_i + C_-\sum_{i\in I_-}\xi_i</script>
</blockquote>
<p>巧的是,班上有个专门研究SVM牛人用这个方法做了,貌似用的是libsvm,也许libsvm能够对每个c值进行修改吧。结果是16%的positive cases!灰常之高,更证明了soft-margin 的svm对这类问题的好处。
而且用SVM有个好处就是你不用使用什么resampling了,反正对svm的影响不大,因为svm 是基于support vectors做判断的。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[音乐乐器声音分类]]></title>
<link href="http://kkx.github.com/blog/2012/05/21/yin-le-le-qi-sheng-yin-fen-lei/"/>
<updated>2012-05-21T23:07:00-07:00</updated>
<id>http://kkx.github.com/blog/2012/05/21/yin-le-le-qi-sheng-yin-fen-lei</id>
<content type="html"><![CDATA[<p>音乐分类其实很早就有人做了,这些天在kaggle上有个<a href="http://www.kaggle.com/c/msdchallenge">Million Song Dataset Challenge</a> 非常火,上面有大量的数据可以让选手们通过底层,高层的和基于用户协作推荐的等等手段来对歌曲进行推荐. 这里我做的音乐声音分类是底层的, 而且我觉得一般的音乐文件要处理的东西太多了,节奏,速度什么的,在这里我只对4种乐器的声音进行分类: 钢琴,吉他,口琴,小提琴。</p>
<p>一般底层的声音,语音分类,speech recognition 使用无外乎<a href="MFCC">Mel-frequency cepstrum</a> 和 lpc analysis。 第二是个比较老的方法,而MFCC是我所用到的。可以把MFCCs对于好分类和识别的家伙们来说可以理解为对一段声音文件的descriptor,重点是这个descriptor非常discriminant. 在实验中,使用了matlab的voicebox里的melcepst函数,可以生成长度为12的数组,每个数组一般是一很小段声音的description,由于取样和设置的问题,MFCC所包含的声音长度是不一样的,在我的实验中,差不多连续1000个MFCC差不多是6秒钟的音频。 那4种声音来源分别是verycd上名家solo哈,不过要吐槽的是找了好多title是solo的,但是其实是是几种乐器和音的。每个类里取了6首左右的歌曲(20分钟左右)作为训练数据,提取出500k的MFCCs。</p>
<p>在实验中我使用了两个方法, 一种是在学校里老师讲的”常规”方法, 还有一种是用于图像分类的。总觉得在做底层分类的时候,图像识别和声音识别的步骤是差不多的。</p>
<ul>
<li>Binary-split, SVM</li>
<li>Naive Bayes Nearest Neighbor </li>
</ul>
<h2 id="binary-split--svm">Binary-split + SVM</h2>
<p>500k+的数据直接用来使用的话那就太夸张了,Binary-split+SVM这方法简单的来说是通过binary-split把500k的MFCC压缩成1000到2000个clusters,通过binary-split类似的数据会在一个小组里面。这种clustering算法里最有名的是k-means,不过k-means 速度实在是太悲剧了,虽然压缩损耗度要比binary-split少一些。 </p>
<p>通过binary-split数据给压缩到少许clusters里,这所有的clusters其实就是所谓的<a href="http://en.wikipedia.org/wiki/Codebook">codebook</a>。codebook在通讯里面非常用,往往在数据通讯里,特别是声音传输中,需要用到codebook把传输数据进行压缩,在接受点的那一端,通过decode来还原原始数据。像一般的手机通讯就是用到这个概念,所以说很多时候人们发现电话里的声音总不是特别像某个人,原因就是因为声音信息在传输的时候多少给损耗掉了。在实验中,每个声音段(通常是1000个MFCCs也就是6秒左右的声音)用codebook可以生成一个一个histogram: 把每个MFCC在binary-split树里跑一下,找到最相近的cluster,通过统计所有cluster里一个声音段落里不同的MFCCs所出现的次数来生成Histogram。</p>
<p>每个声音段所产生的histrogram就是所要用到用来训练SVM的数据。这里的svm是one-vs-all,所以说将有4个svm会训练出来,每个svm会应对每个类,而每个svm会长生出一个概率值,是只一个声音段是它所对应类的可能性。在测试中往往选取最高的可能性作为音频的class。</p>
<p>在测试中,以上的步骤都差不多,只是,不需要生成codebook,直接使用了就可以。这里要说的是,测试的音频长度不一定需要和训练的音频长度一样,只要每次生成的histogram是归一化的就行了。</p>
<table border="1" cellspacing="30" cellpadding="100">
<tr>
<th />
<th />
<th> Piano|</th>
<th> </th>
<th> Guitar| </th>
<th> </th>
<th> Violin| </th>
<th />
<th> Harmonica| </th>
</tr>
<tr>
<th>Piano|</th>
<th />
<th>16</th>
<th />
<th>8</th>
<th />
<th>0</th>
<th />
<th>0</th>
</tr>
<tr>
<th>Guitar|</th>
<th />
<th>4</th>
<th />
<th>12</th>
<th />
<th>20</th>
<th />
<th>4</th>
</tr>
<tr>
<th>Violin|</th>
<th />
<th>0</th>
<th />
<th>0</th>
<th />
<th>20</th>
<th />
<th>4</th>
</tr>
<tr>
<th>Harmonica|</th>
<th />
<th>0</th>
<th />
<th>0</th>
<th />
<th>0</th>
<th />
<th>16</th>
</tr>
</table>
<p><br />
测试的时候每个类有20个case,从结果上来看,钢琴和吉他的识别不是很高,而且他们彼此有着很高互指。其实很大的原因是:当我们用codebook的时候,会对数据进行压缩,很多时候这个压缩会让比较discriminant的数据丢失。在这个实验中,钢琴和吉他的声音是比较相似的。</p>
<p><img class="center" src="http://kkx.github.com/images/histogram1.png" width="650" height="850" />
上面这个图是个histogram的分布图,通过随机选取每个类里的某一个训练的音频段生成的分布图,从途中可以看出,实际上通过codebook之后,吉他和钢琴的数据分布是非常耦合和相似的。所以,实验中对钢琴和吉他的分别度不高也有了依据。</p>
<h2 id="naive-bayes-nearest-neighbornbnn">Naive Bayes Nearest Neighbor(NBNN)</h2>
<p>实际上,在图像识别中,很多时候也是运用到了上面所讲的方法,通过codebook对图像所提取的key-points进行clusterization,之后通过第二层的分类器把codebook产出的histogram数组进行分类。所以我觉得NBNN应该也能运用到声音分类中来。 NBNN是最近以篇叫<a href="http://www.google.es/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CFMQFjAA&url=http%3A%2F%2Fwww.wisdom.weizmann.ac.il%2F~irani%2FPAPERS%2FInDefenceOfNN_CVPR08.pdf&ei=d7G6T6yCKaOu0QX5_cD0Aw&usg=AFQjCNGWk0lo_L6AEjgco6PPMUZ0YE-zOQ&sig2=ZvwoYRICPitYbrkseMV8Bw">In Defense of Nearest-Neighbor Based Image Classification</a>的论文中看到的,该论文着重指出,codebook对数据损耗在无训练模型中的影响。 NBNN就是无训练模型(也叫no parametric?), 优点就是不需要训练。。。还有一个优点是在分类里,NBNN用的是image-class距离,而不是image-image距离,这样的话,intravariance很高的类对它的影响不大。</p>
<p>在论文中如果我们使用的邻近邻居是1的话,算法简化成以下:</p>
<ol>
<li>Compute MFCCs <script type="math/tex">d_i,...,d_n</script> of the query audio</li>
<li><script type="math/tex">\forall d_i \forall C</script> compute the NN of <script type="math/tex">d_i</script> in <script type="math/tex">C</script>: <script type="math/tex">NN_C(d_i)</script></li>
<li><script type="math/tex">C = arg min_c\sum_{i=1}^n</script> <script type="math/tex">\left \|d_i - NN_c(d_i)\right \|^2 </script></li>
</ol>
<p><img class="right" src="http://kkx.github.com/images/kd_tree.png" width="200" height="250" title="kt-tree" />
还有一点要说的是,在寻找最近邻居的时候,计算需求是相当高的,在原文里,作者用的是<a href="http://en.wikipedia.org/wiki/K-d_tree">kd-tree</a>,这样的能很快的找到最近的邻居。在这个实验的时候我用的scipy里kd-tree效率非常慢,后来才发现原来原作者是用的approximate kd-tree. </p>
<p>这个算法虽然简单,但是结果是惊人的不错:
使用所有的训练数据里的mfcc,每个测试音频有2000个MFCC,测试过程相当慢(好几个小时,如果使用approximate kd-tree的话应该能快好几个数量级)</p>
<table border="1" cellspacing="30" cellpadding="100">
<tr>
<th />
<th />
<th> Piano|</th>
<th> </th>
<th> Guitar| </th>
<th> </th>
<th> Violin| </th>
<th />
<th> Harmonica| </th>
</tr>