基于Python Pillow库的马赛克拼图程序
期末需要完成一个Python有关的小项目,对Python图片处理库比较感兴趣,所以选择了这个程序。
基本思路分两步:第一步用request库,爬取搜狗图片库图片,并保存到本地图片库。第二步是根据已有图片,运用pillow库搜索图片库中的图片,拼接出所选图片。
实现效果:
原始图片
一.爬取网页图片
在Python3.6环境下,以搜狗壁纸为爬取对象。
(选择壁纸网站,考虑到后期拼图,壁纸网站提供的图片大小统一,方便拼图)
我选择了壁纸分类,现在开始F12阅读网页代码进行分析。
检查图片元素后发现图片的src存放在img标签下,然后尝试使用requests组件获取此img标签,进而提取图片的src地址再使用 urllib.request.urlretrieve逐个下载图片,从而达到批量获取资料的目的。
import requests
from bs4 import BeautifulSoup
res = requests.get('http://pic.sogou.com/pics/recommend?category=%B1%DA%D6%BD')
soup = BeautifulSoup(res.text,'html.parser')
print(soup.select('img'))
输出:
运行后发现并得不到所需要的图片src,只得到图片的img信息,可能网页采取了动态元素,图片原始src并不在当前链接下。然后开始了对图片原始src的漫长探索过程,最后在多方网友帖子的指导下,找到位置 F12→Network→XHR→点击一个文件的Preview。
然后在bthumbURL标签终于得到了图片的原始链接,在访问这个链接后,确认得到的是所需要的图片。在访问图示链接后,显示的是小图,应该是预览图,后发现原始图片链接在下面ori_pic_url标签下,由于拼图程序对图片质量要求不高,我还是采用图示链接,图片小也方便下载
但是问题出现了,我得不到XHR→preview下的链接,后又在多方查询下,发现Preview标签左边的Header标签里面有网页原始链接。
得到这个Request URL,去掉URL中不需要的部分,分析链接,字面意思,知道category后面可能为壁纸这个大的图片分类,tag后面为壁纸的标签(分类),start为开始下标,len为长度,也即图片的数量。
由此我们可以添加选择图片分类的功能,只需要用户输入tag的分类就完成了。
访问页面得到:
然后在这个网页里,终于可以在bthumbURL标签下得到获取图片的url了
下面是批量下载搜狗壁纸的完整代码:
import requests
import json
import urllib
import os
import sys
def getSogouImage(category,tag,length,path):
n = length
cate = category
print('http://pic.sogou.com/pics/channel/getAllRecomPicByTag.jsp?category='+cate+'&tag='+tag+'&start=0&len='+str(n))
imgs = requests.get('https://pic.sogou.com/pics/channel/getAllRecomPicByTag.jsp?category='+cate+'&tag='+tag+'&start=0&len='+str(n))
jd = json.loads(imgs.text)
jd = jd['all_items']
imgs_urls = []
for j in jd:
imgs_urls.append(j['bthumbUrl'])
m = 0
for img_url in imgs_urls:
print('***** '+str(m)+'.jpg *****'+' Downloading...')
urllib.request.urlretrieve(img_url,path+str(m)+'.jpg')
m = m + 1
print('Download complete!')
workPath = sys.path[0]
if not os.path.exists(workPath + "\imgs"):
os.mkdir(workPath + "\imgs")
tag = input("请输入所下载壁纸分类:(全部,世界风光,动物,明星,汽车,动漫,游戏等):")
amount = input("请输入下载张数(整数):")
print("下载到当前目录img\目录下")
getSogouImage('壁纸', tag, amount, workPath + "\imgs\\")
output:
二.马赛克拼图程序
思路其实很简单,就是将目标图片分割成均匀小块,再从已有的本地图片库中根据RGB,灰度搜索图库中最匹配的图片进行替换,每个小块都替换好了,整个图片也就拼好了。
所以这部分程序分为三个部分:
1.处理原始图片
2.搜索图库进行匹配
3.替换每个小块,生成新的图片
1.处理原始图片
思路是获取到图片的分辨率后,计算出图片的长宽的最大公约数(GCD)。例如原始图片为1920* 1080,GCD为120,则长宽都由,120张图片拼出来,这样计算出来每个小图的尺寸为16* 9。为防止小图尺寸过小,设置一个参数min_unit,为小图长宽的倍数。若min_unit = 5,则此例中小图最终尺寸为80*45,然后每行图片数量为120/5=24张。所以min_unit也可理解为最终图片的精细度,数值越小,每一行的图片越多,最终图片密度越大。(设置了一个flag,如果原图无法正常计算处理,返回flag = false,这个时候需要手动输入参数小图尺寸)
# 计算子图大小
def __divide_sub_im(self, width, height):
flag = True
g = self.__gcd(width, height)
if g < 20:
flag = False
width = self.__default_w
height = self.__default_h
g = 320
if g == width:
g = 320
self.__sub_width = self.__min_unit * (width // g)
self.__sub_height = self.__min_unit * (height // g)
return flag
# 辗转相除法求最大公约数
@staticmethod
def __gcd(a, b):
while a % b:
a, b = b, a % b
return b
2.读取本地图库
参数有3个,本地图库路径,上一个方法确定的小图最终长宽。根据参数,对图库所有图片resize,方便后期小图拼接。
# 读取全部图片保存, fin_w,fin_h素材最终尺寸
def __read_all_img(self, db_path, fin_w, fin_h):
files_name = os.listdir(db_path)
n = 1
# 开启5个线程加载图片
ts = list()
for i in range(5):
ts.append(list())
for file_name in files_name:
full_path = db_path + "\\" + file_name
if os.path.isfile(full_path):
read_task = self.__ReadTask(n, full_path