├── 文档 ├── 1.webp └── 22.webp ├── requirements.txt ├── setup.py ├── readme.md └── loliimg └── __init__.py /文档/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RimoChan/loliimg/HEAD/文档/1.webp -------------------------------------------------------------------------------- /文档/22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RimoChan/loliimg/HEAD/文档/22.webp -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python>=4.5.1.48 2 | numpy>=1.19.5 3 | pyswarms>=1.3.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | setuptools.setup( 5 | name='loliimg', 6 | version='1.0.1', 7 | author='RimoChan', 8 | author_email='the@librian.net', 9 | description='loliimg', 10 | long_description=open('readme.md', encoding='utf8').read(), 11 | long_description_content_type='text/markdown', 12 | url='https://github.com/RimoChan/loliimg', 13 | packages=['loliimg'], 14 | classifiers=[ 15 | 'Programming Language :: Python :: 3', 16 | 'Operating System :: OS Independent', 17 | ], 18 | install_requires=[ 19 | 'opencv-python>=4.5.1.48', 20 | 'numpy>=1.19.5', 21 | 'pyswarms>=1.3.0', 22 | ], 23 | python_requires='>=3.6', 24 | ) 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 【萝莉图片算法】高损图像压缩算法!? 2 | 3 | 我又发明出新算法了! 4 | 5 | 这次我发明的是新型高损图像压缩算法——萝莉图片算法!为什么是萝莉图片,这是因为它是使动用法,让图片变小所以是萝莉图片,大家一定要学好语文哦! 6 | 7 | 8 | ## 压缩效果 9 | 10 | 太神奇了!压缩率竟然高达99.97%! 11 | 12 | ![./文档/22.webp](./文档/22.webp) 13 | 14 | ## 与常见压缩算法对比 15 | 16 | 在图片最终大小为1KB的情况下,我们将萝莉图片与JPG、PNG、WEBP比较—— 17 | 18 | ![./文档/1.jpg](./文档/1.webp) 19 | 20 | 看起来萝莉图片算法的质量还不错! 21 | 22 | 不过其他算法设成最低质量也压不到1KB,所以原图是提前压过分辨率的。 23 | 24 | 25 | ## 原理 26 | 27 | 原理非常简单——如果莉沫酱在白纸上画长方形,只要她画得足够多,就可以画出任何的图像细节。而如果能画得准确,她就可以用少量的数据来表示出画像的主体部分。 28 | 29 | 这里用了一个简单的贪心算法来计算每一个长方形的画法,我们设置一个代价函数作为贪的目标,是让原图和目标图的每个像素的误差的平方和(SE)最小。 30 | 31 | 我们每次暴搜一个长方形的左上角和右下角,假设将原图中长方形内部的所有像素取均值作为压缩图的长方形填充颜色,以此来减小SE。然后把那个最能削减SE的长方形真正地填上。 32 | 33 | 嗯,就是这么简单。 34 | 35 | 对了,顺便一说我写完以后发现这个代价函数好像是可以优化到O(1)的但是我太累了动不了了……下次再写吧。 36 | 37 | 38 | ## 安装和使用方法 39 | 40 | 首先你需要一个Python3.6以上版本。 41 | 42 | 然后—— 43 | ```sh 44 | pip install git+https://github.com/RimoChan/loliimg.git 45 | ``` 46 | 47 | 装好之后在要用的时候import就行啦! 48 | 49 | ```python 50 | import cv2 51 | import loliimg 52 | 53 | img = cv2.imread('ori.png') 54 | res = loliimg.ride(img, 3, verbose=False) 55 | for pos, color in res: 56 | print(pos, color) 57 | ``` 58 | 59 | 打出来之后你就可以拿着`pos`和`color`去干一些别的事情,比如生成SVG之类的。 60 | 61 | 接口只有1个,是`loliimg.ride`,长这样—— 62 | 63 | ```python 64 | def ride(真原图, epoch, 中间结果存储位置=None, verbose=True): 65 | ... 66 | ``` 67 | 68 | 参数第一个是输入的图片,3维度的np.array,第二个是要跑多少轮,第三个是中间结果存储位置,一个Path字符串,第四个是要不要显示每个epoch的进度条。 -------------------------------------------------------------------------------- /loliimg/__init__.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import tqdm 3 | import pyswarms 4 | import numpy as np 5 | 6 | 7 | def q(pr): 8 | x1, y1, x2, y2 = map(int, pr) 9 | if x1 > x2: 10 | x1, x2 = x2, x1 11 | elif x1 == x2: 12 | x2 += 1 13 | if y1 > y2: 14 | y1, y2 = y2, y1 15 | elif y1 == y2: 16 | y2 += 1 17 | return x1, y1, x2, y2 18 | 19 | 20 | m = {} 21 | def 切片方和(img, roi, n, merge=True): 22 | # 替代 (a[roi].flatten()**n).sum(),需要保证img是不变量 23 | d = id(img) 24 | if (d, n) not in m: 25 | img = img**n 26 | s = [*img.shape] 27 | s[0] += 1 28 | s[1] += 1 29 | img2 = np.zeros(shape=s, dtype=img.dtype) 30 | img2[1:, 1:] = img 31 | m[d, n] = np.cumsum(np.cumsum(img2, axis=1), axis=0) 32 | res = _切片方和(d, roi, n) 33 | if merge: 34 | res = res.sum() 35 | return res 36 | 37 | 38 | def _切片方和(d, roi, n): 39 | 前缀和 = m[d, n] 40 | a, b = roi 41 | return 前缀和[a.stop, b.stop]-前缀和[a.start, b.stop]-前缀和[a.stop, b.start]+前缀和[a.start, b.start] 42 | 43 | 44 | def ya(原图, 目标图, i, verbose=False): 45 | w, h = 原图.shape[:2] 46 | 差值图 = 原图-目标图 47 | def 代价(粒子群): 48 | def 粒子代价(pr): 49 | x1, y1, x2, y2 = q(pr) 50 | roi = slice(x1, x2), slice(y1, y2) 51 | size = (x2-x1)*(y2-y1) 52 | t = 切片方和(原图, roi, 1, False) 53 | # 平均颜色 = t / size 54 | 原se = 切片方和(差值图, roi, 2) 55 | # _原se = ((原图[roi]-目标图[roi]).flatten()**2).sum() 56 | # print(-0.1 < 原se - _原se < 0.1) 57 | 新se = 切片方和(原图, roi, 2) - (t/size*t).sum() 58 | return 新se-原se 59 | return [*map(粒子代价, 粒子群)] 60 | options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9} 61 | bounds = ( 62 | [0, 0, 0, 0], 63 | [w-1, h-1, w-1, h-1], 64 | ) 65 | optimizer = pyswarms.single.GlobalBestPSO(n_particles=120+i//5, dimensions=4, options=options, bounds=bounds) 66 | best_cost, best_pos = optimizer.optimize(代价, iters=40+i//15, verbose=verbose) 67 | return best_cost, best_pos 68 | 69 | 70 | def ride(真原图, epoch, 中间结果存储位置=None, resize=None, verbose='tqdm'): 71 | 真原图 = 真原图.astype(np.float64) 72 | 真原w, 真原h = 真原图.shape[:2] 73 | if resize: 74 | 原图 = cv2.resize(真原图, resize, interpolation=cv2.INTER_CUBIC) 75 | else: 76 | 原图 = 真原图.copy() 77 | 目标图 = np.zeros(shape=原图.shape) 78 | 目标图[:] = 原图.mean(axis=(0, 1)).reshape([1, 1, 3]) 79 | 块组 = [] 80 | t = range(epoch) 81 | if verbose == 'tqdm': 82 | t = tqdm.tqdm(t) 83 | verbose = False 84 | for i in t: 85 | best_cost, best_pos = ya(原图, 目标图, i, verbose=verbose) 86 | x1, y1, x2, y2 = q(best_pos) 87 | roi = slice(x1, x2), slice(y1, y2) 88 | 平均颜色 = 原图[roi].mean(axis=(0, 1)) 89 | 块组.append([[x1, y1, x2, y2], 平均颜色.astype(int)]) 90 | 目标图[roi] = np.array(平均颜色).reshape([1, 1, 3]) 91 | 目标图 = 目标图.copy() 92 | if 中间结果存储位置: 93 | 写 = 目标图.astype(np.uint8) 94 | if 写.shape[:2] != (真原w, 真原h): 95 | 写 = cv2.resize(写, (真原h, 真原w)) 96 | cv2.imwrite(f'{中间结果存储位置}/{i}.jpg', 写) 97 | return 块组 98 | --------------------------------------------------------------------------------