2.2 程序原理
本程序根据颜色(肤色)找出图片中皮肤的区域,然后通过一些条件判断是否为色情图片。
程序的关键步骤如下:
HSV 颜色模式
h > 0 and h < 35 and s > 0.23 and s < 0.68
97.5 <= cb <= 142.5 and 134 <= cr <= 176
一幅图像有零个到多个的皮肤区域,程序按发现顺序给它们编号,第一个发现的区域编号为 0,第 n 个发现的区域编号为 n-1
我们用一种类型来表示像素,我们给这个类型取名为 Skin ,包含了像素的一些信息:唯一的 编号( id ),是/否肤色( skin ),皮肤区域号( region ),横坐标( x ),纵坐标( y)
接下来实现细节部分
2.3 实现脚本
在 /home/shiyanlou/ 目录下新建 nude.py 文件,我们将在这个文件中进行代码的编写:
导入所需要的模块
import sys import os import _io from collections import namedtuple from PIL import Image
我们将设计一个 Nude 类:
class Nude(object):
这个类里面我们首先使用 collections.namedtuple 定义一个 Skin 类型
Skin = namedtuple("Skin", "id skin region x y")
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 是其中任意一个类型的实例,返回真,否则假
涉及到效率问题,越大的图片所需要消耗的资源与时间越大,因此有时候可能需要对图片进行缩小
所以需要有图片缩小方法
若当前像素并不是肤色,那么跳过本次循环,继续遍历
# 若当前像素不为肤色像素,跳过此次循环 if not isSkin: continue
若当前像素是肤色像素,那么就需要处理了,先遍历其相邻像素
检测的图像里,有些前几行的像素的相邻像素并没有 4 个,所以需要用 try “试错”
然后相邻像素的若是肤色像素,如果两个像素的皮肤区域号都为有效值且不同,因为两个区域中的像素相邻,那么其实这两个区域是连通的,说明需要合并这两个区域。记录下此相邻肤色像素的区域号,之后便可以将当前像素归到这个皮肤区域里了。
遍历完所有相邻像素后,分两种情况处理
方法 self._merge 便是用来合并这些连通的皮肤区域的
方法 self._analyse_regions ,运用之前在程序原理一节定义的非色情图像判定规则,从而得到判定结果
现在编写我们还没写过的调用过的 Nude 类的方法
首先是 self._classify_skin 方法,这个方法是检测像素颜色是否为肤色,之前在程序原理一节已经把肤色判定该公式列举了出来,现在是用的时候了
颜色模式的转换并不是本实验的重点,转换公式可以在网上找到,这里我们直接拿来用就行
def _to_normalized(self, r, g, b): if r == 0: r = 0.0001 if g == 0: g = 0.0001 if b == 0: b = 0.0001 _sum = float(r + g + b) return [r / _sum, g / _sum, b / _sum] def _to_ycbcr(self, r, g, b): # 公式来源: # http://stackoverflow.com/questions/19459831/rgb-to-ycbcr-conversion-problems y = .299*r + .587*g + .114*b cb = 128 - 0.168736*r - 0.331364*g + 0.5*b cr = 128 + 0.5*r - 0.418688*g - 0.081312*b return y, cb, cr def _to_hsv(self, r, g, b): h = 0 _sum = float(r + g + b) _max = float(max([r, g, b])) _min = float(min([r, g, b])) diff = float(_max - _min) if _sum == 0: _sum = 0.0001 if _max == r: if diff == 0: h = sys.maxsize else: h = (g - b) / diff elif _max == g: h = 2 + ((g - r) / diff) else: h = 4 + ((r - g) / diff) h *= 60