本项目将使用python3去识别图片是否为色情图片,会使用到PIL这个图像处理库,并且编写算法来划分图像的皮肤区域
介绍一下PIL:
PIL(Python Image Library)是一种免费的图像处理工具包,这个软件包提供了基本的图像处理功能,如:改变图像大小,旋转
图像,图像格式转化,色场空间转换(这个我不太懂),图像增强(就是改善清晰度,突出图像有用信息),直方图处理,插值
(利用已知邻近像素点的灰度值来产生未知像素点的灰度值)和滤波等等。虽然这个软件包要实现复杂的图像处理算法并不太适
合,但是python的快速开发能力以及面向对象等等诸多特点使得它非常适合用来进行原型开发。
在 PIL 中,任何一副图像都是用一个 Image 对象表示,而这个类由和它同名的模块导出,因此,要加载一副图像,最简单的形式
是这样的:
import Image
img = Image.open(“dip.jpg”)
注意:第一行的 Image 是模块名;第二行的 img 是一个 Image 对象; Image 类是在 Image 模块中定义的。关于 Image 模
块和 Image 类,切记不要混淆了。现在,我们就可以对 img 进行各种操作了,所有对 img 的 操作最终都会反映到到 dip.img 图
像上
环境准备
PIL 2009 年之后就没有更新了,也不支持 Python3 ,于是有了 Alex Clark 领导的公益项目 Pillow 。Pillow 是一个对 PIL 友好的分
支,支持 Python3,所以我们这里安装的是 Pillow,这是它的官方文档。
默认已经有python3.0以上和包管理工具pip3。那要执行如下命令升级pip3并安装Pillow 工具包:
sudo install -U pip3
sudo install Pillow
程序原理
根据颜色(肤色)找出图片中皮肤的区域,然后通过一些条件判断是否为色情图片。
程序的关键步骤如下:
python学习交流Q群:906715085####
1.遍历每个像素,检查像素颜色是否为肤色
2.将相邻的肤色像素归为一个皮肤区域,得到若干个皮肤区域
3.剔除像素数量极少的皮肤区域
我们定义非色情图片的判定规则如下(满足任意一个判断为真):
1.皮肤区域的个数小于3个
2.皮肤区域的像素与图像所有像素的比值小于15%
3.最大皮肤区域小于总皮肤面积的45%
4.皮肤区域数量超过60个
这些规则你可以尝试更改,直到程序效果让自己满意为止。关于像素肤色判定这方面,公式可以在网上找到很多,但是世界上不
可能有正确率100%的公式。你可以用自己找到的公式,在程序完成后慢慢调试。
•RGB颜色模式
第一种:==r > 95 and g > 40 and g < 100 and b > 20 and max([r, g, b]) - min([r, g, b]) > 15 and abs(r - g) > 15 and r > g and r > b==
第二种:==nr = r / (r + g + b), ng = g / (r + g + b), nb = b / (r +g + b) ,nr / ng > 1.185 and r * b / (r + g + b) ** 2 > 0.107 and r * g / (r + g + b) ** 2 > 0.112==
•HSV颜色模式
==h > 0 and h < 35 and s > 0.23 and s < 0.68==
•YCbCr颜色模式
==97.5 <= cb <= 142.5 and 134 <= cr <= 176==
一幅图像有零个到多个的皮肤区域,程序按发现顺序给它们编号,第一个发现的区域编号为0,第n个发现的区域编号为n-1
用一种类型来表示像素,我们给这个类型取名为Skin,包含了像素的一些信息:唯一的编号id、是/否肤色skin、皮肤区域号
region、横坐标x、纵坐标y
遍历所有像素时,我们为每个像素创建一个与之对应的Skin对象,并设置对象的所有属性,其中region属性即为像素所在的皮肤
区域编号,创建对象时初始化为无意义的None。关于每个像素的id值,左上角为原点,像素id值按照像素坐标排布,那么看起来
如下图:
其实id的顺序也即遍历的顺序。遍历所有像素时,创建Skin对象后,如果当前像素为肤色,且相邻的像素有肤色的,那么我们把
这些肤色像素归到一个皮肤区域。
相邻像素的定义:通常都能想到是当前像素周围的8个像素,然而实际上只需要定义4个就可以了,位置分别在当前像素的左方,
左上方,正上方,右上方。因为另外四个像素都在当前像素后面,我们还未给这4个像素创建对应的Skin对象:
实现脚本
直接在python中新建nude.py文件,在这个文件进行代码编写:
导入所需要的模块:
import sys
import os
import _io
from collections import namedtuple
from PIL import Image
我们将设计一个Nude类:
class Nude:
这个类里面我们首先使用 collections.namedtuple() 定义一个 Skin 类型:
Skin = namedtuple("Skin", "id skin region x y")
collections.namedtuple() 函数实际上是一个返回 Python 中标准元组类型子类的一个工厂方法。你需要传递一个类型名和你需要
的字段给它,然后它就会返回一个类,你可以初始化这个类,为你定义的字段传递值等。详情参见官方文档。
然后定义 Nude 类的初始化方法:
python学习交流Q群:906715085###
def __init__(self, path_or_image):
# 若 path_or_image 为 Image.Image 类型的实例,直接赋值
if isinstance(path_or_image, Image.Image):
self.image = path_or_image
# 若 path_or_image 为 str 类型的实例,打开图片
elif isinstance(path_or_image, str):
self.image = Image.open(path_or_image)
# 获得图片所有颜色通道
bands = self.image.getbands()
# 判断是否为单通道图片(也即灰度图),是则将灰度图转换为 RGB 图
if len(bands) == 1:
# 新建相同大小的 RGB 图像
new_img = Image.new("RGB", self.image.size)
# 拷贝灰度图 self.image 到 RGB图 new_img.paste (PIL 自动进行颜色通道转换)
new_img.paste(self.image)
f = self.image.filename
# 替换 self.image
self.image = new_img
self.image.filename = f
# 存储对应图像所有像素的全部 Skin 对象
self.skin_map = []
# 检测到的皮肤区域,元素的索引即为皮肤区域号,元素都是包含一些 Skin 对象的列表
self.detected_regions = []
# 元素都是包含一些 int 对象(区域号)的列表
# 这些元素中的区域号代表的区域都是待合并的区域
self.merge_regions = []
# 整合后的皮肤区域,元素的索引即为皮肤区域号,元素都是包含一些 Skin 对象的列表
self.skin_regions = []
# 最近合并的两个皮肤区域的区域号,初始化为 -1
self.last_from, self.last_to = -1, -1
# 色情图像判断结果
self.result = None
# 处理得到的信息
self.message = None
# 图像宽高
self.width, self.height = self.image.size
# 图像总像素
self.total_pixels = self.width * self.height
isinstane(object, classinfo) 如果参数 object 是参数 classinfo 的实例,返回真,否则假;参数 classinfo 可以是一个包含若干 type
对象的元组,如果参数 object 是其中任意一个类型的实例,返回真,否则假。
涉及到效率问题,越大的图片所需要消耗的资源与时间越大,因此有时候可能需要对图片进行缩小。所以需要有图片缩小方法:
def resize(self, maxwidth=1000, maxheight=1000):
"""
基于最大宽高按比例重设图片大小,
注意:这可能影响检测算法的结果
如果没有变化返回 0
原宽度大于 maxwidth 返回 1
原高度大于 maxheight 返回 2
原宽高大于 maxwidth, maxheight 返回 3
maxwidth - 图片最大宽度
maxheight - 图片最大高度
传递参数时都可以设置为 False 来忽略