利用人工智能检测色情图片 - CSDN博客
色情内容在中国一直处于严格的监管,即使这样,互联网上还是很容易就能访问到色情内容。还记得曾经的“绿坝-花季护航”软件么?由于其识别效果差、软件不稳定,最后不了了之,浪费了大量的人力和金钱。
随着计算机视觉和深度学习的发展,算法已经成熟,利用人工智能,我们能够更加精确的识别色情内容。现在有很多云服务商提供鉴黄服务,通过集成鉴黄API到产品中,就可以给产品增加色情过滤功能。这种模式对于大多数互联网产品非常有效,可以极大的降低运营风险(对于博客、图床、直播等服务提供商而言,过滤色情、暴力等内容是重中之重)。但对于一部分产品而言,这种模式存在一些不足:
- 鉴别图片、视频内容等必须通过网络服务进行,响应速度难以保证;
- 通常鉴黄服务按次或者按照流量计费,对于个人开发者而言,有成本负担;
open_nsfw
现在有个好消息,雅虎开源了其深度学习色情图片检测模型open_nsfw,项目地址: http://github.com/yahoo/open_nsfw,这里的NSFW代表Not Suitable for Work。项目提供了基于caffe的深度神经网络模型和一个python脚本,可以供测试:
python ./classify_nsfw.py \
--model_def nsfw_model/deploy.prototxt \--pretrained_model nsfw_model/resnet_50_1by2_nsfw.caffemodel \test_image.jpg
定义NSFW内容是非常主观的,在某个国家或地区会引起反感的内容可能在另一个地方完全没问题。所以,open_nsfw模型只关注一种类型的NSFW内容:色情图片。但需要注意的是,该模型不能解决草图、漫画、文本等内容的识别。
色情图片的判别也是非常主观的,所以该模型并不会直接给出某个图片是否色情的结果,而是给出一个概率(0-1之间的分数)。一般而言,得分小于0.2表示图像很可能是安全的,评分大于0.8则基本可判定图片属于色情图片。如果得分介于这两个值之间,则需要程序员根据需求来设定一个阀值。如果需要比较严格的过滤,设一个比较低的阀值,反之设一个比较高的阀值。
集成open_nsfw到C++程序
open_nsfw提供了一个python脚本,google了一圈,也没有找到有人将open_nsfw集成到C++代码中。好在classify_nsfw.py这个脚本比较简单,而caffe提供了C++的例子cpp_classification,结合这两部分的代码,我编写了一个C++的鉴黄程序,源码参考: https://gitee.com/mogoweb/dpexamples。
当然看似简单的代码翻译工作,遇到的坑也不少,下面就总结一下C++代码中需要注意的地方。
图片预处理
在classify_nsfw.py中,编写了一个resize_image函数来处理图片缩放,没有采用caffe内置的图片缩放程序。代码注释中解释是因为训练时使用了这个缩放算法,所以为了达到最好的效果,测试中也需要采用该缩放算法。随后又对图片进行了一次裁剪,因为调用resize_image缩放为256x256大小,而模型接受的size为224x224。
img_data_rs = resize_image(pimg, sz=(256,256))image= caffe.io.load_image(StringIO(img_data_rs))
H, W, _ =image.shape
_, _, h, w = caffe_net.blobs['data'].data.shape
h_off =max((H - h) /2,0)
w_off =max((W - w) /2,0)
crop =image[h_off:h_off + h, w_off:w_off + w, :]
在C++代码中,我使用了caffe中提供的opencv方法处理这个步骤:
cv::Mat img = ReadImageToCVMat(file,256,256);
...// crop imagecv::Sizesize= sample.size();intH =size.height;intW =size.width;inth = input_geometry_.height;intw = input_geometry_.width;inth_off = std::max((H - h) /2,0);intw_off = std::max((W - w) /2,0);
sample(cv::Rect(w_off, h_off, w, h)).copyTo(sample_resized);
数据预处理
在classify_nsfw.py中,我们可以看到这样一段代码:
caffe_transformer= caffe.io.Transformer({'data': nsfw_net.blobs['data'].data.shape})# move image channels to outermostcaffe_transformer.set_transpose('data',(2, 0, 1))# subtract the dataset-mean value in each channelcaffe_transformer.set_mean('data', np.array([104, 117, 123]))# rescale from [0, 1] to [0, 255]caffe_transformer.set_raw_scale('data', 255)# swap channels from RGB to BGRcaffe_transformer.set_channel_swap('data',(2, 1, 0))
这段代码其实是对数据做某种变换,将读入的数据转换为caffe模型能够接受的格式。
caffe_transformer.set_transpose('data',(2, 0, 1))
该语句困扰了我好长时间,不知道在C++程序中该如何处理。后查网络资料,才了解到因为caffe.io读取的图像为HWC(H:高,W:宽,C:颜色)矩阵,而caffe模型需要(CHW)格式,所以需要对矩阵做一个变换,将颜色值维度提前到最前面。(2, 0, 1)的含义就是将原来数据的第2, 0, 1列按照新的顺序重新排列。
caffe_transformer.set_raw_scale('data', 255)
这个处理是因为使用caffe.io读取的颜色值归一化为0~1之间的数,而caffe模型接受的是一个字节的整数,范围0~255,所以需要进行一个放大。
在C++代码中,由于采用了opencv进行图像处理,不需要上面两步的变换处理。
caffe_transformer.set_mean('data', np.array([104, 117, 123]))
在很多示例中,均值通常从均值文件中加载,这里直接给了一个固定值。所谓均值,可以理解为数据的算术平均值。通常输入数据减去均值,是为了减少奇异值(异常的值,比平均值大很多或小很多的值)的影响。这里是一个三元组,分别代表RGB通道上的均值。
对应的C++代码如下:
cv::Matsample_normalized;cv::subtract(sample_float, mean_, sample_normalized);
接下来的代码是RGB转BGR,这个比较容易理解。
caffe_transformer.set_channel_swap('data',(2, 1, 0))
查看了一些caffe的C++例子,均没有这个步骤,可能cv::Mat中已经能够正确判断出RGB和BGR,所以代码中没有增加这一步骤。
对比测试
这个程序是否能够如愿工作呢?让我们找一些图片测试一下。考虑到内容审查,这里进行测试的图片均不是严格意义上的色情图片,只是裸露程度不同。下面使用C++程序和open_nsfw python脚本测试的结果进行对比。
C++ : 0.6122
python: 0.875480949879
C++ : 0.2536
python: 0.0773676931858
C++ : 0.6319
python: 0.782215833664
C++ : 0.0914
python: 0.0774072110653
C++ : 0.0073
python: 3.04092318402e-05
从结果可以看出,使用C++程序进行测试,结果基本符合预期,但是和python版本还是有一些差距,猜测问题可能在于对图片进行缩放采用的算法不同,如果要获得好的结果,训练和测试阶段对数据的预处理需要一致。另外一个可能是没有RGB到BGR的转换,这个还需要再验证。
如果你有兴趣,可以找一些尺度更大的图片测试,看看是不是能够正确的识别出来。