代码逻辑
-
resize调整大小
为了实现保证长宽比的前提下调整到规定的大小范围,这里先对宽度进行调整,计算maxwidth/width的比例,再计算相应的高度。再比较maxheight与调整后的高度,如果还是过大再按照相同的方法调整宽度。两个if判断后,宽度和高度均满足要求,并保证了图片没有拉伸或压缩。
但是原函数在这里返回了一个状态值(如果没有变化返回 0;原宽度大于 maxwidth 返回 1;原高度大于 maxheight 返回 2;原宽高大于 maxwidth, maxheight 返回 3)不过这个返回值没有看到应用,我也没想到可能的作用,这就很迷惑。 -
分析图片
这步是为了实现两个目的:1是判断每一个像素是否为肤色;2是为整张图片的像素划分区域,找到实际上相连的区域保存起来。-
判断像素是否为肤色相对比较简单:
公式可以在网上找到很多,但世界上不可能有正确率 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
-
-
划分区域乍一听挺懵的,仔细想想还是很巧妙的:
对于每一个判定为肤色的像素遍历它周围4个已经判断是否为肤色的像素,如果哪一个元素判断为肤色像素,就可以认定两个元素相连,即同属于一个区域,那么把这个元素的区域号赋给当前像素。如果在遍历中发现当前像素的区域号已经赋值且与它周围的某一个肤色像素的区域号不同,这说明它周围的像素分别属于两个不同的区域,而这两个区域通过当前像素连接起来了!这个时候需要把这两个像素号保存下来,方便后续进行连接操作。 -
对于非肤色的像素,要进行的就只有保存一个skin元组(区域号-1,是否为肤色False,坐标x,y),在遍历其他元素的时候使用。然而对于肤色像素,除了要保存skin元组(区域号,是否为肤色True,坐标x,y)之外,还需在region列表和mergebox列表中写入相关信息。
skin元组构成的list存储的是每一个像素的信息;而region列表存储的就是肤色像素的信息,每一个元素中保存的是相同区域号的像素的skin元组;mergebox中保存的就是需要连接的区域号。
因此在划分区域的过程中如果当前像素与周围像素的区域号相同,被赋了某个值,那就需要在region列表的对应区域后加入skin信息;而如果所有相邻像素都不是肤色像素,在region列表的最后加入skin信息。if region==None: region=self.count self.count+=1 self.skin_map.append(self.skin(region, True, x, y)) self.region.append([self.skin_map[id]]) else: self.skin_map.append(self.skin(region, True, x, y)) self.region[region].extend([self.skin_map[id]])
在这里我保存了一个全局变量,每次赋值之后就更新一次,原函数用了一个非常巧妙的方法,让region=len(self.region)这样就自然的保存为下一位的区域号。emmm,学到了学到了。
-
-
合并需要连接的区域:
一开始在我的设想中是要遍历一遍所有像素的skin元组,同时还要伴随mergebox的遍历,这得是多大的计算量,时间复杂度太高了。但是原始代码中设计region列表存放了肤色像素的信息,就是为了避免重复的遍历!在第一次遍历就保存了所有肤色像素后,合并需要连接的区域就变成了简单列表的连接操作。
for index,merge_list in enumerate(self.mergebox): for number in merge_list: try: self.mergedRegion[index].extend(self.region[number]) except: self.mergedRegion.append(self.region[number]) self.region[number]=[]
与region的赋值方式相同,没在列表中出现则在list最后append,出现过则在对应index处extend。
-
分析是否为色情图片:
我们定义非色情图片的判定规则如下(满足任意一个判定为真):
- 皮肤区域的个数小于 3 个
- 皮肤区域的像素与图像所有像素的比值小于 15%
- 最大皮肤区域小于总皮肤面积的 45%
- 皮肤区域数量超过 60 个
知识点
- namedtuple:
collections.namedtuple构造了一个具名元组,具名元组消耗的内存与普通元组一样多。
创建一个User对象user = User(name=‘Student’, sex=‘male’, age=12)
也可以通过一个list来创建一个User对象 user = User._make([‘Student’, ‘male’, 12])
获取属性 print( user.name ) print(user[0]) - Image.getbans(): Example:an RGB image returns (“R”, “G”, “B”)
- Image.resize(size) 返回的是当前对象的拷贝,因此如果想对原始对象操作一定要赋值回去!
- Image.resize(size) 返回的是当前对象的拷贝,因此如果想对原始对象操作一定要赋值回去!
- Image.LANCZOS:重采样滤波器,用于Image.resize,抗锯齿