OSCHINA上看到各种语言的抓妹子图的程序段,拿来跑一跑,都是爬虫的机制,而地址一般都是固定的,格式固定,才能抓到想要的图,这显示不够智能,于是把作者的代码改掉,变成了个下载图片的爬虫。然后问题就来了,大量的图片,不是我想要的,于是想到了图像识别,目前主要的分支有,找相似图,人脸识别,鉴黄等。
OSCHINA上看到各种语言的抓妹子图的程序段,拿来跑一跑,都是爬虫的机制,而地址一般都是固定的,格式固定,才能抓到想要的图,这显示不够智能,于是把作者的代码改掉,变成了个下载图片的爬虫。然后问题就来了,大量的图片,不是我想要的,就这想到了图像识别,目前主要的分支有,找相似图,人脸识别,鉴黄等。
今天要说说肤色提取,大概就暴露了,我要选什么分支了,不多说,不多说 >_<!
肤色提取
开始使用了CSDN上某大神写的一段JAVA代码(用于检测黄色图片),使用了YUV色彩空间。效果还是很不错的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * flesh * * @param c * @return */ public static boolean isFlesh( final Color c) { if ((c.getRed() > 230 ) && (c.getGreen() > 170 ) && (c.getBlue() > 190 )) { return false ; } LDialyzer yuv = LDialyzer.getYuv(c.getRed(), c.getGreen(), c.getBlue()); return ((c.getRed() > 40 ) && (c.getGreen() > 40 ) && (yuv.y + 16 > 145 ) && (yuv.v + 128 < 173 ) && (yuv.v + 128 > 133 ) && (yuv.u + 128 < 127 ) && (yuv.u + 128 > 77 )); } |
但是这段代码,上半部分的依据RGB范围直接PASS掉一部分,这确定是有点果断的,仔细观察RGB色彩空间,会发现还是有一部分的偏黄色被排除了。于是考虑使用HSV色彩空间。
HSV六棱锥
H参数表示色彩信息,即所处的光谱颜色的位置。该参数用一角度量来表示,红、绿、蓝分别相隔120度。互补色分别相差180度。
纯度S为一比例值,范围从0到1,它表示成所选颜色的纯度和该颜色最大的纯度之间的比率。S=0时,只有灰度。
V表示色彩的明亮程度,范围从0到1。有一点要注意:它和光强度之间并没有直接的联系。
RGB转化到HSV的算法
max=max(R,G,B)
min=min(R,G,B)
if R = max, H = (G-B)/(max-min)
if G = max, H = 2 + (B-R)/(max-min)
if B = max, H = 4 + (R-G)/(max-min)
H = H * 60
if H < 0, H = H + 360
V=max(R,G,B)
S=(max-min)/max
HSV转化到RGB的算法
if s = 0
R=G=B=V
else
H /= 60;
i = INTEGER(H)
f = H - i
a = V * ( 1 - s )
b = V * ( 1 - s * f )
c = V * ( 1 - s * (1 - f ) )
switch(i)
case 0: R = V; G = c; B = a;
case 1: R = b; G = v; B = a;
case 2: R = a; G = v; B = c;
case 3: R = a; G = b; B = v;
case 4: R = c; G = a; B = v;
case 5: R = v; G = a; B = b;
由算法,写JAVA实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static HSV RGB2HSV(RGB rgb){ float r = ( float )rgb.getR()/ 255 ; float g = ( float )rgb.getG()/ 255 ; float b = ( float )rgb.getB()/ 255 ; float max = max(r, g, b); float min = min(r, g, b); float h = 0 ; if (r==max) h = (g-b)/(max-min); if (g==max) h = 2 +(b-r)/(max-min); if (b==max) h= 4 +(r-g)/(max-min); h *= 60 ; if (h< 0 ) h += 360 ; HSV hsv = new HSV(h,(max-min)/max,max); return hsv; } |
对于肤色识别 饱和度(S)和亮度(V)就无关紧要了,这样只需得到一个色调(H)的取值范围。
从网上找了找H的取值范围,大概在25~50,为了近一步确定这个数值,做了如下实验。
先扣了一些美女图,只要肉,尽量选择有差异的。
JAVA实现统计
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 | public static int [] vessel = new int [ 360 ]; public static int [] vesselIndex = new int [ 360 ]; public static void main(String[] args) throws IOException { File file = new File( "D:\\培养材料" ); File[] listFiles = file.listFiles(); ArrayList<HSV> list = new ArrayList<HSV>(); for ( int i = 0 ; i < listFiles.length; i++) { transition(listFiles[i]); } for ( int i = 0 ; i < vesselIndex.length; i++) { vesselIndex[i] = i; } for ( int i = 0 ; i < vessel.length; i++) { for ( int j = i+ 1 ; j < vessel.length; j++) { if (vessel[i]<vessel[j]){ int temp = vessel[i]; vessel[i] = vessel[j]; vessel[j] = temp; int tempIndex = vesselIndex[i]; vesselIndex[i] = vesselIndex[j]; vesselIndex[j] = tempIndex; } } } for ( int i = 0 ; i < vesselIndex.length; i++) { System.out.println( "H=" +vesselIndex[i]+ ",count:" +vessel[i]); } } private static ArrayList<HSV> transition(File file) throws IOException{ System.out.println(file.getName()); BufferedImage img = ImageIO.read(file); ArrayList<HSV> list = new ArrayList<HSV>(); for ( int j = 0 ; j <img.getWidth(); j++) { for ( int j2 = 0 ; j2 < img.getHeight(); j2++) { int binaryColor = img.getRGB(j, j2); if (binaryColor== 16777215 ) continue ; Color c = new Color(binaryColor); RGB rgb = new RGB(c.getRed(), c.getGreen(), c.getBlue()); HSV hsv = ColorUtils.RGB2HSV(rgb); if (! "NaN" .equals(String.valueOf(hsv.getH()))) vessel[( int )hsv.getH()]++; list.add(hsv); System.out.println(hsv); } } return list; } |
结果:(略掉count=0)
H=15,count:31071
H=18,count:26936
H=16,count:24615
H=13,count:24031
H=17,count:21968
H=12,count:21211
H=30,count:19438
H=38,count:16740
H=14,count:16470
H=33,count:16404
H=32,count:16217
H=28,count:15231
H=35,count:14929
H=20,count:14714
H=31,count:14353
H=36,count:13654
H=29,count:13515
H=21,count:13311
H=34,count:13133
H=19,count:12595
H=26,count:11921
H=10,count:11062
H=37,count:10669
H=11,count:10422
H=27,count:9726
H=22,count:9010
H=25,count:8629
H=24,count:8548
H=40,count:8375
H=23,count:8240
H=39,count:7295
H=41,count:4262
H=43,count:3365
H=0,count:3229
H=9,count:2628
H=60,count:1983
H=42,count:1469
H=8,count:1453
H=7,count:927
H=44,count:862
H=45,count:742
H=180,count:515
H=51,count:354
H=48,count:263
H=240,count:221
H=330,count:210
H=6,count:198
H=47,count:168
H=50,count:147
H=56,count:137
H=5,count:134
H=63,count:125
H=52,count:116
H=46,count:90
H=69,count:69
H=220,count:59
H=76,count:57
H=70,count:50
H=77,count:44
H=4,count:41
H=64,count:36
H=184,count:32
H=75,count:32
H=72,count:30
H=49,count:29
H=354,count:27
H=353,count:26
H=280,count:25
H=2,count:25
H=150,count:24
H=120,count:23
H=68,count:23
H=352,count:19
H=350,count:17
H=3,count:16
H=55,count:15
H=54,count:14
H=90,count:13
H=65,count:12
H=79,count:11
H=357,count:11
H=210,count:10
H=351,count:10
H=251,count:10
H=74,count:9
H=356,count:9
H=53,count:9
H=190,count:8
H=67,count:8
H=300,count:8
H=73,count:8
H=348,count:8
H=57,count:8
H=185,count:7
H=345,count:7
H=83,count:7
H=78,count:7
H=66,count:7
H=355,count:6
H=188,count:6
H=228,count:6
H=100,count:5
H=340,count:5
H=336,count:4
H=85,count:4
H=84,count:4
H=171,count:3
H=186,count:3
H=173,count:3
H=140,count:3
H=195,count:3
H=349,count:3
H=105,count:3
H=108,count:2
H=174,count:2
H=96,count:2
H=182,count:2
H=183,count:2
H=82,count:2
H=95,count:2
H=165,count:2
H=170,count:2
H=189,count:2
H=106,count:2
H=358,count:2
H=260,count:1
H=264,count:1
H=94,count:1
H=144,count:1
H=88,count:1
H=1,count:1
H=166,count:1
H=342,count:1
H=187,count:1
H=168,count:1
H=110,count:1
H=114,count:1
H=192,count:1
H=172,count:1
H=92,count:1
H=128,count:1
H=175,count:1
H=176,count:1
H=249,count:1
H=135,count:1
分析数据,H的范围大概在9~43之间
验证以上分析
public static void main(String[] args) throws IOException {
BufferedImage dst = new BufferedImage(100, 360 * 5,
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < 100; i++) {
//for (int j = 0; j < 360 * 5; j++) {
for (int j = 0; j < 50 * 5; j++) {
dst.setRGB(i, j, ColorUtils.RGB2Binary(ColorUtils.HSV2RGB(new HSV(j/5, 1, 1))));
}
}
ImageIO.write(dst, "jpg", new File("D:\\hsv1.jpg"));
}
结果 (略掉未绘制部分)
H范围[0,50),很显示以上数据,上下可以再切掉10%~30%。这是当S,V都等于1时的图像,尝试修改S和V的值,范围在[0,1],就可以匹配到因光线等问题,造成的较亮或较暗的图像。而在做肤色匹配时,不考虑S和V,使准确性提高。
判断鲜肉
public static boolean isFlesh2(Color c){ RGB rgb = new RGB(c.getRed(),c.getGreen(),c.getBlue()); HSV hsv = ColorUtils.RGB2HSV(rgb); if(hsv.getH()>9&&hsv.getH()<43){ return true; } return false; }