python找色_利用python检测色情图片简易实例

1 importsys2 importos3 import_io4 from collections importnamedtuple5 from PIL importImage6

7 classNude(object):8

9 Skin = namedtuple("Skin", "id skin region x y")10

11 def __init__(self, path_or_image):12 #若 path_or_image 为 Image.Image 类型的实例,直接赋值

13 ifisinstance(path_or_image, Image.Image):14 self.image =path_or_image15 #若 path_or_image 为 str 类型的实例,打开图片

16 elifisinstance(path_or_image, str):17 self.image =Image.open(path_or_image)18

19 #获得图片所有颜色通道

20 bands =self.image.getbands()21 #判断是否为单通道图片(也即灰度图),是则将灰度图转换为 RGB 图

22 if len(bands) == 1:23 #新建相同大小的 RGB 图像

24 new_img = Image.new("RGB", self.image.size)25 #拷贝灰度图 self.image 到 RGB图 new_img.paste (PIL 自动进行颜色通道转换)

26 new_img.paste(self.image)27 f =self.image.filename28 #替换 self.image

29 self.image =new_img30 self.image.filename =f31

32 #存储对应图像所有像素的全部 Skin 对象

33 self.skin_map =[]34 #检测到的皮肤区域,元素的索引即为皮肤区域号,元素都是包含一些 Skin 对象的列表

35 self.detected_regions =[]36 #元素都是包含一些 int 对象(区域号)的列表

37 #这些元素中的区域号代表的区域都是待合并的区域

38 self.merge_regions =[]39 #整合后的皮肤区域,元素的索引即为皮肤区域号,元素都是包含一些 Skin 对象的列表

40 self.skin_regions =[]41 #最近合并的两个皮肤区域的区域号,初始化为 -1

42 self.last_from, self.last_to = -1, -1

43 #色情图像判断结果

44 self.result =None45 #处理得到的信息

46 self.message =None47 #图像宽高

48 self.width, self.height =self.image.size49 #图像总像素

50 self.total_pixels = self.width *self.height51

52 def resize(self, maxwidth=1000, maxheight=1000):53 """

54 基于最大宽高按比例重设图片大小,55 注意:这可能影响检测算法的结果56

57 如果没有变化返回 058 原宽度大于 maxwidth 返回 159 原高度大于 maxheight 返回 260 原宽高大于 maxwidth, maxheight 返回 361

62 maxwidth - 图片最大宽度63 maxheight - 图片最大高度64 传递参数时都可以设置为 False 来忽略65 """

66 #存储返回值

67 ret =068 ifmaxwidth:69 if self.width >maxwidth:70 wpercent = (maxwidth /self.width)71 hsize = int((self.height *wpercent))72 fname =self.image.filename73 #Image.LANCZOS 是重采样滤波器,用于抗锯齿

74 self.image =self.image.resize((maxwidth, hsize), Image.LANCZOS)75 self.image.filename =fname76 self.width, self.height =self.image.size77 self.total_pixels = self.width *self.height78 ret += 1

79 ifmaxheight:80 if self.height >maxheight:81 hpercent = (maxheight /float(self.height))82 wsize = int((float(self.width) *float(hpercent)))83 fname =self.image.filename84 self.image =self.image.resize((wsize, maxheight), Image.LANCZOS)85 self.image.filename =fname86 self.width, self.height =self.image.size87 self.total_pixels = self.width *self.height88 ret += 2

89 returnret90

91 #分析函数

92 defparse(self):93 #如果已有结果,返回本对象

94 if self.result is notNone:95 returnself96 #获得图片所有像素数据

97 pixels =self.image.load()98 #遍历每个像素

99 for y inrange(self.height):100 for x inrange(self.width):101 #得到像素的 RGB 三个通道的值

102 #[x, y] 是 [(x,y)] 的简便写法

103 r = pixels[x, y][0] #red

104 g = pixels[x, y][1] #green

105 b = pixels[x, y][2] #blue

106 #判断当前像素是否为肤色像素

107 isSkin = True if self._classify_skin(r, g, b) elseFalse108 #给每个像素分配唯一 id 值(1, 2, 3...height*width)

109 #注意 x, y 的值从零开始

110 _id = x + y * self.width + 1

111 #为每个像素创建一个对应的 Skin 对象,并添加到 self.skin_map 中

112 self.skin_map.append(self.Skin(_id, isSkin, None, x, y))113 #若当前像素不为肤色像素,跳过此次循环

114 if notisSkin:115 continue

116

117 #设左上角为原点,相邻像素为符号 *,当前像素为符号 ^,那么相互位置关系通常如下图

118 #***

119 #*^

120

121 #存有相邻像素索引的列表,存放顺序为由大到小,顺序改变有影响

122 #注意 _id 是从 1 开始的,对应的索引则是 _id-1

123 check_indexes = [_id - 2, #当前像素左方的像素

124 _id - self.width - 2, #当前像素左上方的像素

125 _id - self.width - 1, #当前像素的上方的像素

126 _id - self.width] #当前像素右上方的像素

127 #用来记录相邻像素中肤色像素所在的区域号,初始化为 -1

128 region = -1

129 #遍历每一个相邻像素的索引

130 for index incheck_indexes:131 #尝试索引相邻像素的 Skin 对象,没有则跳出循环

132 try:133 self.skin_map[index]134 exceptIndexError:135 break

136 #相邻像素若为肤色像素:

137 ifself.skin_map[index].skin:138 #若相邻像素与当前像素的 region 均为有效值,且二者不同,且尚未添加相同的合并任务

139 if (self.skin_map[index].region != None and

140 region != None and region != -1 and

141 self.skin_map[index].region != region and

142 self.last_from != region and

143 self.last_to !=self.skin_map[index].region) :144 #那么这添加这两个区域的合并任务

145 self._add_merge(region, self.skin_map[index].region)146 #记录此相邻像素所在的区域号

147 region =self.skin_map[index].region148 #遍历完所有相邻像素后,若 region 仍等于 -1,说明所有相邻像素都不是肤色像素

149 if region == -1:150 #更改属性为新的区域号,注意元祖是不可变类型,不能直接更改属性

151 _skin = self.skin_map[_id - 1]._replace(region=len(self.detected_regions))152 self.skin_map[_id - 1] =_skin153 #将此肤色像素所在区域创建为新区域

154 self.detected_regions.append([self.skin_map[_id - 1]])155 #region 不等于 -1 的同时不等于 None,说明有区域号为有效值的相邻肤色像素

156 elif region !=None:157 #将此像素的区域号更改为与相邻像素相同

158 _skin = self.skin_map[_id - 1]._replace(region=region)159 self.skin_map[_id - 1] =_skin160 #向这个区域的像素列表中添加此像素

161 self.detected_regions[region].append(self.skin_map[_id - 1])162 #完成所有区域合并任务,合并整理后的区域存储到 self.skin_regions

163 self._merge(self.detected_regions, self.merge_regions)164 #分析皮肤区域,得到判定结果

165 self._analyse_regions()166 returnself167

168

169 #self.merge_regions 的元素都是包含一些 int 对象(区域号)的列表

170 #self.merge_regions 的元素中的区域号代表的区域都是待合并的区域

171 #这个方法便是将两个待合并的区域号添加到 self.merge_regions 中

172 def_add_merge(self, _from, _to):173 #两个区域号赋值给类属性

174 self.last_from =_from175 self.last_to =_to176

177 #记录 self.merge_regions 的某个索引值,初始化为 -1

178 from_index = -1

179 #记录 self.merge_regions 的某个索引值,初始化为 -1

180 to_index = -1

181

182

183 #遍历每个 self.merge_regions 的元素

184 for index, region inenumerate(self.merge_regions):185 #遍历元素中的每个区域号

186 for r_index inregion:187 if r_index ==_from:188 from_index =index189 if r_index ==_to:190 to_index =index191

192 #若两个区域号都存在于 self.merge_regions 中

193 if from_index != -1 and to_index != -1:194 #如果这两个区域号分别存在于两个列表中

195 #那么合并这两个列表

196 if from_index !=to_index:197 self.merge_regions[from_index].extend(self.merge_regions[to_index])198 del(self.merge_regions[to_index])199 return

200

201 #若两个区域号都不存在于 self.merge_regions 中

202 if from_index == -1 and to_index == -1:203 #创建新的区域号列表

204 self.merge_regions.append([_from, _to])205 return

206 #若两个区域号中有一个存在于 self.merge_regions 中

207 if from_index != -1 and to_index == -1:208 #将不存在于 self.merge_regions 中的那个区域号

209 #添加到另一个区域号所在的列表

210 self.merge_regions[from_index].append(_to)211 return

212 #若两个待合并的区域号中有一个存在于 self.merge_regions 中

213 if from_index == -1 and to_index != -1:214 #将不存在于 self.merge_regions 中的那个区域号

215 #添加到另一个区域号所在的列表

216 self.merge_regions[to_index].append(_from)217 return

218

219 #合并该合并的皮肤区域

220 def_merge(self, detected_regions, merge_regions):221 #新建列表 new_detected_regions

222 #其元素将是包含一些代表像素的 Skin 对象的列表

223 #new_detected_regions 的元素即代表皮肤区域,元素索引为区域号

224 new_detected_regions =[]225

226 #将 merge_regions 中的元素中的区域号代表的所有区域合并

227 for index, region inenumerate(merge_regions):228 try:229 new_detected_regions[index]230 exceptIndexError:231 new_detected_regions.append([])232 for r_index inregion:233 new_detected_regions[index].extend(detected_regions[r_index])234 detected_regions[r_index] =[]235

236 #添加剩下的其余皮肤区域到 new_detected_regions

237 for region indetected_regions:238 if len(region) >0:239 new_detected_regions.append(region)240

241 #清理 new_detected_regions

242 self._clear_regions(new_detected_regions)243

244 #皮肤区域清理函数

245 #只保存像素数大于指定数量的皮肤区域

246 def_clear_regions(self, detected_regions):247 for region indetected_regions:248 if len(region) > 30:249 self.skin_regions.append(region)250

251 #分析区域

252 def_analyse_regions(self):253 #如果皮肤区域小于 3 个,不是色情

254 if len(self.skin_regions) < 3:255 self.message = "Less than 3 skin regions ({_skin_regions_size})".format(256 _skin_regions_size=len(self.skin_regions))257 self.result =False258 returnself.result259

260 #为皮肤区域排序

261 self.skin_regions = sorted(self.skin_regions, key=lambdas: len(s),262 reverse=True)263

264 #计算皮肤总像素数

265 total_skin = float(sum([len(skin_region) for skin_region inself.skin_regions]))266

267 #如果皮肤区域与整个图像的比值小于 15%,那么不是色情图片

268 if total_skin / self.total_pixels * 100 < 15:269 self.message = "Total skin percentage lower than 15 ({:.2f})".format(total_skin / self.total_pixels * 100)270 self.result =False271 returnself.result272

273 #如果最大皮肤区域小于总皮肤面积的 45%,不是色情图片

274 if len(self.skin_regions[0]) / total_skin * 100 < 45:275 self.message = "The biggest region contains less than 45 ({:.2f})".format(len(self.skin_regions[0]) / total_skin * 100)276 self.result =False277 returnself.result278

279 #皮肤区域数量超过 60个,不是色情图片

280 if len(self.skin_regions) > 60:281 self.message = "More than 60 skin regions ({})".format(len(self.skin_regions))282 self.result =False283 returnself.result284

285 #其它情况为色情图片

286 self.message = "Nude!!"

287 self.result =True288 returnself.result289

290 #基于像素的肤色检测技术

291 def_classify_skin(self, r, g, b):292 #根据RGB值判定

293 rgb_classifier = r > 95 and\294 g > 40 and g < 100 and\295 b > 20 and\296 max([r, g, b]) - min([r, g, b]) > 15 and\297 abs(r - g) > 15 and\298 r > g and\299 r >b300 #根据处理后的 RGB 值判定

301 nr, ng, nb =self._to_normalized(r, g, b)302 norm_rgb_classifier = nr / ng > 1.185 and\303 float(r * b) / ((r + g + b) ** 2) > 0.107 and\304 float(r * g) / ((r + g + b) ** 2) > 0.112

305

306 #HSV 颜色模式下的判定

307 h, s, v =self._to_hsv(r, g, b)308 hsv_classifier = h > 0 and\309 h < 35 and\310 s > 0.23 and\311 s < 0.68

312

313 #YCbCr 颜色模式下的判定

314 y, cb, cr =self._to_ycbcr(r, g, b)315 ycbcr_classifier = 97.5 <= cb <= 142.5 and 134 <= cr <= 176

316

317 #效果不是很好,还需改公式

318 #return rgb_classifier or norm_rgb_classifier or hsv_classifier or ycbcr_classifier

319 returnycbcr_classifier320

321 def_to_normalized(self, r, g, b):322 if r ==0:323 r = 0.0001

324 if g ==0:325 g = 0.0001

326 if b ==0:327 b = 0.0001

328 _sum = float(r + g +b)329 return [r / _sum, g / _sum, b /_sum]330

331 def_to_ycbcr(self, r, g, b):332 #公式来源:

333 #http://stackoverflow.com/questions/19459831/rgb-to-ycbcr-conversion-problems

334 y = .299*r + .587*g + .114*b335 cb = 128 - 0.168736*r - 0.331364*g + 0.5*b336 cr = 128 + 0.5*r - 0.418688*g - 0.081312*b337 returny, cb, cr338

339 def_to_hsv(self, r, g, b):340 h =0341 _sum = float(r + g +b)342 _max =float(max([r, g, b]))343 _min =float(min([r, g, b]))344 diff = float(_max -_min)345 if _sum ==0:346 _sum = 0.0001

347

348 if _max ==r:349 if diff ==0:350 h =sys.maxsize351 else:352 h = (g - b) /diff353 elif _max ==g:354 h = 2 + ((g - r) /diff)355 else:356 h = 4 + ((r - g) /diff)357

358 h *= 60

359 if h <0:360 h += 360

361

362 return [h, 1.0 - (3.0 * (_min / _sum)), (1.0 / 3.0) *_max]363

364 definspect(self):365 _image = '{} {} {}×{}'.format(self.image.filename, self.image.format, self.width, self.height)366 return "{_image}: result={_result} message='{_message}'".format(_image=_image, _result=self.result, _message=self.message)367

368 #将在源文件目录生成图片文件,将皮肤区域可视化

369 defshowSkinRegions(self):370 #未得出结果时方法返回

371 if self.result isNone:372 return

373 #皮肤像素的 ID 的集合

374 skinIdSet =set()375 #将原图做一份拷贝

376 simage =self.image377 #加载数据

378 simageData =simage.load()379

380 #将皮肤像素的 id 存入 skinIdSet

381 for sr inself.skin_regions:382 for pixel insr:383 skinIdSet.add(pixel.id)384 #将图像中的皮肤像素设为白色,其余设为黑色

385 for pixel inself.skin_map:386 if pixel.id not inskinIdSet:387 simageData[pixel.x, pixel.y] =0, 0, 0388 else:389 simageData[pixel.x, pixel.y] = 255, 255, 255

390 #源文件绝对路径

391 filePath =os.path.abspath(self.image.filename)392 #源文件所在目录

393 fileDirectory = os.path.dirname(filePath) + '/'

394 #源文件的完整文件名

395 fileFullName =os.path.basename(filePath)396 #分离源文件的完整文件名得到文件名和扩展名

397 fileName, fileExtName =os.path.splitext(fileFullName)398 #保存图片

399 simage.save('{}{}_{}{}'.format(fileDirectory, fileName,'Nude' if self.result else 'Normal', fileExtName))400

401 if __name__ == "__main__":402 importargparse403

404 parser = argparse.ArgumentParser(description='Detect nudity in images.')405 parser.add_argument('files', metavar='image', nargs='+',406 help='Images you wish to test')407 parser.add_argument('-r', '--resize', action='store_true',408 help='Reduce image size to increase speed of scanning')409 parser.add_argument('-v', '--visualization', action='store_true',410 help='Generating areas of skin image')411

412 args =parser.parse_args()413

414 for fname inargs.files:415 ifos.path.isfile(fname):416 n =Nude(fname)417 ifargs.resize:418 n.resize(maxheight=800, maxwidth=600)419 n.parse()420 ifargs.visualization:421 n.showSkinRegions()422 print(n.result, n.inspect())423 else:424 print(fname, "is not a file")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值