├── KMCompress.py ├── KMCompress_tf.py ├── README.md ├── a.jpg ├── b.jpg ├── compress_a.jpg └── compress_b.jpg /KMCompress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2017/8/28 下午6:18 3 | # @Author : fj 4 | # @Site : 5 | # @File : KMCompress.py 6 | # @Software: PyCharm 7 | 8 | import numpy as np 9 | from PIL import Image 10 | import random 11 | 12 | class KMCompress(): 13 | 14 | def __init__(self): 15 | self.centroids = None #簇中心 (K, c) 16 | self.data = None #压缩后的数据 (h*w, c) 17 | self.cen_idx = None #压缩后的数据对应簇中心的下标(h*w, 1) 18 | self.shape = None #原图的shape 19 | 20 | def compress(self, path, K=16, iter=3): 21 | ''' 22 | 压缩图像 23 | :param path: 图像路径 24 | :param K: K簇,压缩后图像使用K个像素点表示全图 25 | :param iter: 迭代计算次数 26 | :return: None 27 | ''' 28 | X, self.shape = self.__image_to_matrix(path) 29 | centroids = self.__init_random_centroids(X, K) 30 | for i in range(iter): 31 | print(' ====> ' + str((i+1.0)*100 / (iter + 1)) + '%') 32 | cen_idx = self.__find_closest_centroids(X, centroids) 33 | centroids = self.__move_centroids(X, cen_idx, K) 34 | print(' ====> 99%') 35 | cen_idx = self.__find_closest_centroids(X, centroids) 36 | 37 | self.cen_idx = cen_idx 38 | self.centroids = centroids 39 | self.__fill_data() 40 | 41 | 42 | def __fill_data(self): 43 | m = self.cen_idx.shape[0] 44 | channel = self.shape[2] 45 | self.data = np.zeros((m, channel)) 46 | for i in range(m): 47 | self.data[i] = self.centroids[int(self.cen_idx[i])] 48 | 49 | def __image_to_matrix(self, path): 50 | ''' 51 | 加载图片 52 | :param path: 53 | :return: 图像矩阵 (h * w, c) 54 | ''' 55 | img = Image.open(path) 56 | w, h = img.size 57 | if img.mode == 'RGB': 58 | shape = (h, w, 3) 59 | elif img.mode == 'L': 60 | shape = (h, w, 1) 61 | return np.asmatrix(img.getdata(), dtype='float'), shape 62 | 63 | 64 | def __init_random_centroids(self, X, K): 65 | ''' 66 | 初始化簇中心 67 | :param X: 图像矩阵 (h*w, c) 68 | :param K: 69 | :return: 簇中心(K, c) 70 | ''' 71 | m, n = X.shape 72 | sh = list(range(m)) 73 | random.shuffle(sh) 74 | centroids = [] 75 | for i in range(K): 76 | centroids.append(X[sh[i]]) 77 | return (np.asarray(centroids))[:,0,:] #list 转换成Matrix 78 | 79 | 80 | def __find_closest_centroids(self, img, centroids): 81 | ''' 82 | 计算图像每个像素点对应的簇中心下标 83 | :param img: 84 | :param centroids: (h*w, 1) 85 | :return: 图像每个像素点对应的簇中心下标 86 | ''' 87 | m, n = img.shape 88 | K, _ = centroids.shape 89 | cen_idx = np.zeros([m, 1], dtype='int') 90 | for i in range(m): 91 | M = np.full([K, n], img[i]) 92 | err = M - centroids 93 | distance = np.multiply(err, err).sum(axis=1) 94 | cen_idx[i] = distance.argmin() #最小列索引 95 | return cen_idx 96 | 97 | 98 | def __move_centroids(self, img, cen_idx, K): 99 | ''' 100 | 更新簇中心 101 | :param img: 102 | :param cen_idx: 图像每个像素点对应的簇中心下标 103 | :param K: K簇 104 | :return: 簇中心矩阵 (K, c) 105 | ''' 106 | m, n = img.shape 107 | times = np.zeros([K, 1], dtype='int') 108 | centroids = np.zeros([K, n], dtype='float') 109 | for i in range(m): 110 | idx = int(cen_idx[i]) 111 | centroids[idx] = centroids[idx] + img[i] 112 | times[idx] = times[idx] + 1 113 | while times.min() == 0: #避免除零异常 114 | times[times.argmin()] = 1 115 | centroids = centroids / times 116 | return centroids 117 | 118 | 119 | def get_img(self): 120 | ''' 121 | 返回图像文件 122 | :param M: 123 | :param shape: 124 | :return: 125 | ''' 126 | img = np.reshape(np.asarray(self.data), self.shape) 127 | return Image.fromarray(img.astype(np.uint8)) 128 | 129 | 130 | 131 | def save(self, path): 132 | ''' 133 | #TODO 字典+二进制压缩存储图片 134 | :param path: 135 | :return: 136 | ''' 137 | pass 138 | 139 | 140 | def load(self, path): 141 | ''' 142 | TODO 读取压缩后的图片 143 | :param path: 144 | :return: 145 | ''' 146 | pass 147 | 148 | a = KMCompress() 149 | a.compress('./b.jpg', iter=10) 150 | a.get_img().save('./compress_b.jpg') 151 | print('done...') -------------------------------------------------------------------------------- /KMCompress_tf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2017/9/5 下午10:44 3 | # @Author : fj 4 | # @Site : 5 | # @File : kmeans_net.py 6 | # @Software: PyCharm 7 | 8 | import tensorflow as tf 9 | import numpy as np 10 | import time 11 | from PIL import Image 12 | 13 | 14 | def TFKmeans(vect, K, iter=3): 15 | K = int(K) 16 | assert K < len(vect) 17 | m, n =len(vect), len(vect[0]) 18 | init_idx = list(range(m)) 19 | np.random.shuffle(init_idx) 20 | centroids = [vect[init_idx[i]] for i in range(K)] 21 | graph = tf.Graph() 22 | with graph.as_default(): 23 | sess = tf.Session() 24 | 25 | op_centroids = tf.placeholder(dtype=tf.float32, shape=[K, n]) 26 | op_cent_idx = tf.Variable(dtype=tf.int64, initial_value=init_idx) 27 | 28 | # 单个像素点组成的[K, n]矩阵 29 | v1 = tf.placeholder(dtype=tf.float32, shape=[K, n]) 30 | 31 | # 使用v1矩阵计算单个像素点到所有簇中心的距离 32 | op_distances = tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(v1, op_centroids), 2), axis=1)) 33 | op_idx = tf.argmin(op_distances, axis=0) 34 | 35 | # 所有像素点分簇后 重新计算出簇中心的算子 36 | cluster_input = tf.placeholder(dtype=tf.float32, shape=[None, 3]) 37 | reduce_new_cent = tf.reduce_mean(cluster_input, axis=0) 38 | 39 | ################################ 40 | # 到此为止,已经构建好图中所有需要的算子 41 | ################################ 42 | sess.run(tf.global_variables_initializer()) 43 | 44 | for it in range(iter): 45 | print('iter = ' + str(it + 1)) 46 | for j in range(m): 47 | print(j) 48 | v = vect[j] 49 | # 对于当前像素 计算它到每一个簇中心的距离 50 | M = np.full(shape=[K, n], fill_value=v) 51 | idx = sess.run(op_idx, feed_dict={v1: M, op_centroids: centroids}) 52 | # 取距离最近的那个簇中心的索引 53 | # idx = sess.run(op_idx, feed_dict={each_centroid_distances: distances}) 54 | # 将索引保存在cent_idx[j]中 55 | value = sess.run(op_cent_idx) 56 | value[j] = idx 57 | sess.run(tf.assign(ref=op_cent_idx, value=value)) 58 | # sess.run(cent_idx[j], feed_dict={cent_idx_value: idx}) 59 | 60 | #聚类完毕后, 更新centroids 61 | for j in range(K): 62 | input = [vect[i] for i in range(m) if sess.run(op_cent_idx)[i] == j] 63 | new_centroid_value = sess.run(reduce_new_cent, feed_dict={cluster_input: input}) 64 | c = sess.run(op_centroids) 65 | c[j] = new_centroid_value 66 | sess.run(tf.assign(ref=op_centroids, value=c)) 67 | 68 | return sess.run(op_centroids), sess.run(op_cent_idx) 69 | 70 | 71 | def image_to_vect(path): 72 | img = np.array(Image.open(path), dtype=np.float32) 73 | m, n, channel = img.shape 74 | return img.reshape(m*n, channel), img.shape 75 | 76 | 77 | def save_compressed_img(M, path, shape): 78 | img = M.reshape(shape) 79 | Image.fromarray(img.astype(np.uint8)).save(path) 80 | 81 | 82 | def compress(path, K=16, iter=3): 83 | vect, shape = image_to_vect(path=path) 84 | tim = time.time() 85 | print(tim) 86 | centroids, cent_idx = TFKmeans(vect, K=16) 87 | print(tim - time.time()) 88 | for i in range(): 89 | vect[i] = centroids[int(cent_idx[i])] 90 | save_compressed_img(vect, path='compress.jpg', shape=shape) 91 | 92 | 93 | compress('./a.jpg') 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageCompress 2 | K-means聚类算法实现图片的压缩,Python实现 3 | 4 | ### 更新 5 | * 2018-03-19 修改代码格式,封装成对象 6 | * 2017-09-06 增加使用TensorFlow实现(实测速度比纯python+numpy慢) 7 | 8 | ### 参数 9 | * K 全图用多少种颜色表示,默认值 K=16,设置K后将会对所有图像进行聚类,并只使用K种颜色表示全图 10 | * iter 迭代计算次数, 默认值 iter=3 11 | 12 | ### 未实现 13 | * 未实现压缩后的存储(降低图像文件大小),只能存为一般jpg格式预览压缩后效果 14 | 15 | ### 效果 16 | 默认参数下的效果 17 | 18 | ![第一张图片](https://github.com/SherlockUnknowEn/ImageCompress/blob/master/a.jpg "第一张图片") 19 | 20 | ![第一张图片压缩](https://github.com/SherlockUnknowEn/ImageCompress/blob/master/compress_a.jpg "第一张图片压缩") 21 | 22 | ![第二张图片](https://github.com/SherlockUnknowEn/ImageCompress/blob/master/b.jpg "第二张图片") 23 | 24 | ![第二张图片压缩](https://github.com/SherlockUnknowEn/ImageCompress/blob/master/compress_b.jpg "第二张图片压缩") -------------------------------------------------------------------------------- /a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SherlockUnknowEn/ImageCompress/f1dafee76e193c163d6735de3b4cd983e18f2a1c/a.jpg -------------------------------------------------------------------------------- /b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SherlockUnknowEn/ImageCompress/f1dafee76e193c163d6735de3b4cd983e18f2a1c/b.jpg -------------------------------------------------------------------------------- /compress_a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SherlockUnknowEn/ImageCompress/f1dafee76e193c163d6735de3b4cd983e18f2a1c/compress_a.jpg -------------------------------------------------------------------------------- /compress_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SherlockUnknowEn/ImageCompress/f1dafee76e193c163d6735de3b4cd983e18f2a1c/compress_b.jpg --------------------------------------------------------------------------------