念念不忘,必有回响

五彩斑斓的黑

五彩斑斓的黑

项目背景

由于众所周知的原因(武汉2020),只能在家整东西玩,想起了以前和同学聊天提到的五彩斑斓的黑,遂来了灵感,造出来这么一个轮子。

项目效果图

项目简介

五彩斑斓的黑,可以把黑白的论文变成五彩斑斓,这样看论文的时候就不无聊了(误

实现了pdf转化为五颜六色的pdf,其实对于其他类型图片的处理也是一样的

项目开源地址

我的github,求路过的朋友点个star吧,提PR那是更好的!

项目依赖

  1. 必备:opencv 图像处理的基础库
  2. 必备:wand 是imagemagick的前端
  3. 必备:imagemagick 基础库
  4. 选配: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

  1. 异步返回处理结果(因为算法有点慢
  2. 使用OSS减轻网络io负担
  3. 更多文件图片格式支持

后记

还有什么好玩的想法可以私聊我呀(
大家试着可以一起实现一下

posted on 2020-02-05 23:21  licsber  阅读(1677)  评论(2编辑  收藏  举报