五彩斑斓的黑
项目背景
由于众所周知的原因(武汉2020
),只能在家整东西玩,想起了以前和同学聊天提到的五彩斑斓的黑
,遂来了灵感,造出来这么一个轮子。
项目效果图
项目简介
五彩斑斓的黑,可以把黑白的论文变成五彩斑斓,这样看论文的时候就不无聊了(误
实现了pdf转化为五颜六色
的pdf,其实对于其他类型图片的处理也是一样的
项目开源地址
我的github,求路过的朋友点个star吧,提PR那是更好的!
项目依赖
- 必备:opencv 图像处理的基础库
- 必备:wand 是imagemagick的前端
- 必备:imagemagick 基础库
- 选配:flask 用于搭建一个服务器在线批量转换
算法介绍
读取pdf
借助wand可以实现pdf转换为jpg图片形式,这样方便使用opencv处理:
def parse_pdf(filepath, resolution=300):
pdf = wi(filename=filepath, resolution=resolution)
pdf = pdf.convert("jpeg")
return pdf
转换完毕的pdf批量保存为文件,因为没有找到wand与opencv的联通格式,所以采用文件作保存处理。
def save_pdf_as_img(pdf, filename):
page_count = 1
for img in pdf.sequence:
page = wi(image=img)
page.save(filename=filename + str(page_count) + '.jpg')
page_count += 1
return page_count
生成彩色图像
这里偷了个懒,利用等差数列乘以一个等比数列,采用opencv的热力图applyColorMap()方法变为彩色,再横竖相加获取随机但仍有一定规律的彩虹图像。
@memoize
def get_color_img(width, height):
w = np.logspace(0, 255, height, base=1.01, dtype=np.uint8)
w = np.reshape(w, (-1, 1))
h = np.linspace(0, 255, width, dtype=np.uint8)
h = np.reshape(h, (1, -1))
shu = w * h
shu = np.reshape(shu, (height, -1))
shu = cv2.cvtColor(shu, cv2.COLOR_GRAY2BGR)
shu = cv2.applyColorMap(shu, cv2.COLORMAP_HSV)
w = np.linspace(0, 255, height, dtype=np.uint8)
w = np.reshape(w, (-1, 1))
h = np.logspace(0, 255, width, base=1.01, dtype=np.uint8)
h = np.reshape(h, (1, -1))
heng = w * h
heng = np.reshape(heng, (height, -1))
heng = cv2.cvtColor(heng, cv2.COLOR_GRAY2BGR)
heng = cv2.applyColorMap(heng, cv2.COLORMAP_HSV)
img = heng + shu
img = cv2.medianBlur(img, 101)
return img
可以看出,使用权重比较大的中值滤波很好的平滑了图像。
因为替换模版的不变性,所以我们利用python的装饰器在内存中保存这个图像,具体可能我会写一篇关于python高级特性:装饰器的文章,不过还是有可能咕咕咕了,这里就把装饰器理解为一个参数是函数的函数就好了。
def memoize(func):
cache = dict()
def memoized_func(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return memoized_func
图像的混合
利用蒙版(mask)
技术,可以完美的实现黑色文字的替换,或者可以自行更改这个要替换的颜色,或者颜色范围,以实现更花里胡哨的效果。
def mix_img(file_dir, filename, count):
path = os.path.join(file_dir, filename)
for i in range(1, count):
img = cv2.imread(path + str(i) + '.jpg')
color = get_color_img(img.shape[1], img.shape[0])
mask = (img == (0, 0, 0))[:, :, 0]
img[mask] = color[mask]
mix = img
cv2.imwrite(path + str(i) + '.jpg', mix)
pdf的生成
混合完的图像还是利用wand复原为pdf文件,生成的pdf可能会比较大,因为变成了纯图像。
def save_img_as_pdf(file_dir, filename, count, output_dir, output_filename):
path = os.path.join(file_dir, filename)
output_path = os.path.join(output_dir, output_filename)
with wi() as w:
for i in range(1, count):
with wi(filename=path + str(i) + '.jpg') as page:
w.sequence.append(page)
w.save(filename=output_path)
return output_path
网络端接受上传
首先让我们5秒写个网页来接受用户pdf文件的输入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>五彩斑斓的黑 - Licsber</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body class="container">
<div class="jumbotron">
<h3 id="header">{{message}}</h3>
<h2>欢迎试用一键五彩斑斓的黑,支持上传pdf。</h2>
<h2>创意By Licsber、Mikewang000000。</h2>
<form id="form1" method="post" action="/api/upload" enctype="multipart/form-data">
<div>
<input id="File1" type="file" name="myfile" class="btn btn-default btn-lg">
<button type="submit" class="btn btn-info btn-lg">提交</button>
</div>
</form>
</div>
</body>
<script>
var heading = $("#header")[0];
setInterval(function () {
if (heading.style.display == "block") {
heading.style.display = "none";
} else if (heading.style.display == "none") {
heading.style.display = "block";
}
}, 1000);
</script>
</html>
闪烁部分是后来写的,主要网页表单部分真的只写了五秒。
serve一下主页:
@app.route('/')
def upload_test():
message = ''
return render_template(UPLOAD_HTML, message=message)
然后写一个flask的函数来接收文件并保存,这个函数,嘿嘿,发现了什么东西没,不管符不符合都先保存再说,方便日志记录。
@app.route('/api/upload', methods=['POST'], strict_slashes=False)
def api_upload():
f = request.files['myfile']
if f:
old_name = f.filename
ext = old_name.rsplit('.', 1)[1]
unix_time = int(time.time())
new_filename = old_name + str(unix_time) + '.' + ext
print(new_filename)
path = os.path.join(file_dir, new_filename)
f.save(path)
else:
message = '你没上传文件哦'
return render_template(UPLOAD_HTML, message=message)
if f and allowed_file(f.filename):
pdf = entity.Pdf(file_dir, new_filename, OUTPUT_PATH)
return downloader(pdf.get_output_filename())
else:
message = '文件类型不支持哦 重新上传试试呢'
return render_template(UPLOAD_HTML, message=message)
下载文件就简单了,flask自带这个方法。
@app.route("/download/<path:filename>")
def downloader(filename):
dir_path = os.path.join(app.root_path, 'output')
return send_from_directory(dir_path, filename, as_attachment=True)
足够的抽象
Java程序员表示,看见什么都想给它抽象成一个类:
class Pdf:
def __init__(self, file_dir, filename, output_path):
self.file_dir = file_dir
self.filename = filename
self.pdf = pdf.parse_pdf(filepath=os.path.join(file_dir, filename))
self.page_count = 0
self.output_path = output_path
def extract(self, tmp_dir='tmp/'):
return pdf.save_pdf_as_img(pdf=self.pdf, filename=tmp_dir + self.filename)
def convert(self):
if self.page_count == 0:
return
color.mix_img(file_dir='tmp/', filename=self.filename, count=self.page_count)
return
def save(self):
return pdf.save_img_as_pdf(file_dir='tmp/', filename=self.filename,
count=self.page_count, output_dir=self.output_path, output_filename=self.filename)
def get_output_filename(self):
self.page_count = self.extract()
self.convert()
self.save()
return self.filename
本来想随机填充的,发现随机生成的图像虽然随机,但是不好看。
TODOS
- 异步返回处理结果(因为算法有点慢
- 使用
OSS
减轻网络io负担 - 更多文件图片格式支持
后记
还有什么好玩的想法可以私聊我呀(
大家试着可以一起实现一下