- 最近在實驗樓做的小項目,這個項目主要用的庫有argparse(命令行解析工具),PIL(python標準圖像庫),os(文件處理)。
- 首先把網址貼出來 https://www.shiyanlou.com/courses/1041/labs/4832/document/
- 素材下載:http://labfile.oss.aliyuncs.com/courses/1041/test-data.zip
- 說下基本原理:簡單來說就是圖片馬賽克化處理,處理效果就是圖片像打了馬賽克一樣;
- 我們將目標圖像劃分成較小圖像的網格,並用適當的圖像替換網格中的每一小塊,創建原始圖像的照片馬賽克。你可以指定網格的尺寸,並選擇輸入圖像是否可以在馬賽克中重複使用。
實驗思路
整體思路很簡單,源代碼中函數比較多,代碼300行左右(講道理我覺得比2048那個簡單很多)2048遊戲python解析,不是很難,瞭解基礎語法後,就可以學習這個。可以跟着實驗樓的學習思路學習源代碼,記得做課後作業,課後作業非常能提高你的編程能力,並且加深對代碼的理解。很多時候,自己跟着打一遍,印象不夠深刻,只有自己開始動腦筋,纔會有大的提高。對代碼會有一個更深的理解。
import argparse#命令行參數解析
import os
import numpy as np
from PIL import Image #標準python圖形處理庫
def getImages(imageDir):
"""
從給定目錄中加載所有替換圖像
@param {str} imageDir 目錄路徑
@return {List[Image]}
"""
#將imageDir中的文件放入一個列表,file就是一個文件名字組成的列表
files = os.listdir(imageDir)
images = []
for file in files:
#得到文件的絕對路徑
filePath = os.path.abspath(os.path.join(imageDir, file))
try:
#讀文件
fp = open(filePath, 'rb')
#打開文件
im = Image.open(fp)#爲了節約電腦資源,利用 Image.open() 將文件句柄 fp 傳入 PIL。圖像加載完成後,立即關閉文件句柄釋放系統資源
#確定圖像信息
images.append(im)
#加載圖像
im.load()
#關閉文件
fp.close()
except:
# ?????
#print('Invalid images: ' , filePath)
print("Invalid image: %s" % (filePath,))
return images
def getAverageRGB(image):
"""
計算圖像的平均RGB值
將圖形中包含的每個像素點的R\G\B 值分別累加,然後除以像素點數,得到圖形的平均R G B
@param {Image} image PIL Image 對象
@return {Tuple[int, int, int]} 平均RGB值
"""
#計算像素點數
npixels = image.size[0] * image.size[1]
#獲得圖像每種顏色及其計數的
#[(c1,(r1, g1, b1)), (c2, (r2, g2, b2)), ...]
cols = image.getcolors(npixels)
#獲得每種顏色的R G B累加值
#[(c1 *r1, c1 * g1, c1 * b1),(c2 *r2, c2 * g2, c2 * b2), ...]
sumRGB = [(x[0] * x[1][0], x[0] * x[1][1], x[0] * x[1][2]) for x in cols]
#計算所有顏色的R,G,B平均值
#x = (c1 *r1, c2 *r2, ...),
#(c1 * g1, c2 *g2, ...),
#(c1 *b1, c2 *b2, ...)
avg = tuple([int(sum(x) /npixels) for x in zip(*sumRGB)])
return avg
def splitImage(image, size):
"""
將圖像按照網格分成多個小圖像
@param {Image} image Pil Image
@return {List[Image]} 小圖像列表
"""
#獲得原始圖像尺寸
W, H = image.size[0], image.size[1]
# print(image.size)
# print(size[1])
#分劃的小圖像個數 m * n
m, n = size
#小圖像的尺寸
w, h = int(W / n), int(H / m)
#分割好的小圖像放在列表中
imgs = []
#遍歷
for j in range(m):
for i in range(n):
#得到小圖像並加入到imgs列表
imgs.append(image.crop((i * w, j *h, (i + 1) * w, (j + 1) * h)))
return imgs
def getBestMatchIndex(input_avg, avgs):
"""
找出顏色最接近的索引
@param {Tuple[int, int, int]} input_avg 目標顏色值
@param {List[Tuple[int, int, int]]} avgs 要搜索的顏色值列表
@return {int} 命中元素的索引
"""
index = 0
min_index = 0
#距離初始化爲無窮大
min_dist = float("inf")
dis = []
for val in avgs:
dist = (val[0] - input_avg[0]) * (val[0] - input_avg[0]) + (val[1] - input_avg[1]) * (val[1] - input_avg[1]) + (val[2] - input_avg[2]) * (val[2] - input_avg[2])
if dist < min_dist:
min_dist = dist
min_index = index
index += 1
dis.append(dist)
return min_index
def createImageGrid(images, dims):
"""
將圖形列表裏的小圖像按先行後列的順序拼接爲一個大圖像
@param {List[Image]} images 小圖像列表
@param {Tuple[int, int]} dims 大圖像的行數和列數
@return Image 拼接得到的大圖像
"""
m, n = dims
#確保小圖像個數滿足要求
assert m *n == len(images)
#計算小圖像的最大尺寸
width = max([img.size[0] for img in images])
height = max([img.size[1] for img in images])
#創建大圖像對象
grid_img = Image.new('RGB', (n * width , m * height ))
#添加3*3像素的小黑快,置於粘貼像素之間,產生不一樣的效果
gap = Image.new('RGB', (3, 3))
#將小圖像粘貼到大圖像
for index in range(len(images)):
#計算要粘貼到的行
row = int(index / n)
#計算粘貼到的列
col = index - n * row
#粘貼
grid_img.paste(images[index], (col * width , row * height ) )
#將小黑快粘貼好
grid_img.paste(gap, (col * width , row * height ))
return grid_img
def createPhotomosaic(target_image, input_images, grid_size, reuse_images = True):
"""
圖片馬賽克生成
@param {Image} target_image 目標圖形
@param {List[Image]} input_images 替換圖像列表
@param {Tuple[int, int]} grid_size 網格行數和列數
@param{bool} reuse_images 是否重複使用替換圖像
@return {Image} 馬賽克圖像
"""
#將目標圖像切成網格小圖像
print('Spliting input image ...')
#x分割好的小圖像列表
target_images = splitImage(target_image, grid_size)
#
print('finding image matches ...')
output_images = []
#分10組進行
count = 0
batch_size = int(len(target_images) / 10)
#計算替換圖像列表裏每個圖像的顏色平均值
avgs = []
for img in input_images:
avgs.append(getAverageRGB(img))
#
for img in target_images:
avg = getAverageRGB(img)
#z找到最匹配的小圖像,添加到 output_images中
match_index = getBestMatchIndex(avg, avgs)
output_images.append(input_images[match_index])
#完成一組,打印進度信息???
if count > 0 and batch_size > 10 and count % batch_size == 0:
print('processsed %d of %d...' %(count, len(target_images)))
count += 1
#不允許重用替換圖像,用過後,就從列表裏移除
if not reuse_images:
input_images.remove(match)
#將output_images圖像按照網格大小拼接成圖像
print('creating mosaic...')
mosaic_image = createImageGrid(output_images, grid_size)
return mosaic_image
def main():
#定義程序接收的命令行參數
parser = argparse.ArgumentParser(
description = 'Cteate a photomosaic from input images')
parser.add_argument('--target-image', dest = 'target_image', required = True)
parser.add_argument('--input-folder', dest = 'input_folder',required = True)
parser.add_argument('--grid-size', nargs = 2,
dest = 'grid_size', required = True)
parser.add_argument('--output-file', dest = 'outfile', required = False)
#解析命令行參數
args = parser.parse_args()
#網格大小
grid_size = (int(args.grid_size[0]), int(args.grid_size[1]))
#馬賽克圖像保存路徑,默認爲 mosaic.png
output_filename = 'mosaic.png'
if args.outfile:
ourput_filename = args.outfile
#打開目標圖像
print('reaing target image...')
target_image =Image.open(args.target_image)
#加載替換圖像
print('reading input images...')
input_images = getImages(args.input_folder)
#如果替換圖形列表爲空 那麼退出
if input_images == []:
print('No input images found in '+ args.input_folder)
exit()
#將替換圖像放到指定的網格大小
print('restzing images ...')
dims = (int(target_image.size[0] / grid_size[1]),
int(target_image.size[1] / grid_size[0]))
for img in input_images:
img.thumbnail(dims)
#生成馬賽克圖像
print('starting photomosaic creation...')
mosaic_image = createPhotomosaic(target_image, input_images, grid_size)
#保存圖像
mosaic_image.save(output_filename, 'PNG')
print("saved output to %s" % (output_filename,))
print('Done!')
if __name__ == '__main__':
main()
沒有做課後題的源碼的效果圖:
-第二道題目做法:在createImageGrid
函數中創建 3*3像素的小黑塊, 語句是:gap = Image.new('RGB', (3, 3))
,當然你想要其他的小塊也可以,把語句更改爲:gap = Image.new('RGB', (3, 3),(255, 255, 255))
(這就是小白塊了),輸入自己想要的R、G、B值,就可以隨心所欲更改漸變顏色了。當粘貼替換圖片小塊後,繼續貼小黑塊語句是:grid_img.paste(gap, (col * width , row * height ))
,就產生了想要的效果。
- 小黑塊效果圖:
小白塊效果圖:
- 第一道題目:
得到圖片的塊狀版本。
思路:將圖片分割成若干小塊,然後再粘貼成完整圖像。
注意:如果分割好了,直接全部粘貼,那麼這個圖象是幾乎看不出變化的,我在粘貼的時候僅僅要了原來的四分之一,然後在進行粘貼,這樣塊狀的感覺就出來了。
源代碼:
"""
打開圖像,分割圖像,將分割好的小塊粘貼(可以只要原來的四分之一,來降低像素),保存。
"""
import os
from PIL import Image
#圖像路徑
target_image1 = '.\\test-data\\專屬.jpg'
#分割圖形
def splitImage(image, size):
"""
將圖像按照網格分成多個小圖像
@param {Image} image Pil Image
@return {List[Image]} 小圖像列表
"""
#image = Image.open(target_image)
#獲得原始圖像尺寸
W, H = image.size[0], image.size[1]
# print(image.size)
# print(size[1])
#分劃的小圖像個數 m * n
m, n = size
#小圖像的尺寸
w, h = int(W / n), int(H / m)
#分割好的小圖像放在列表中
imgs = []
#遍歷
for j in range(m):
for i in range(n):
#得到小圖像並加入到imgs列表
imgs.append(image.crop((2 * i * w, 2 * j *h, (2 * i + 1) * w, (2 * j + 1) * h)))
return imgs
#粘貼圖像
def createImageGrid(images, dims):
"""
將圖形列表裏的小圖像按先行後列的順序拼接爲一個大圖像
@param {List[Image]} images 小圖像列表
@param {Tuple[int, int]} dims 大圖像的行數和列數
@return Image 拼接得到的大圖像
"""
m, n = dims
# #確保小圖像個數滿足要求
# assert m *n == len(images)
#計算小圖像的最大尺寸
width = max([img.size[0] for img in images])
height = max([img.size[1] for img in images])
#創建大圖像對象
grid_img = Image.new('RGB', (int(n * width / 2), int(m * height / 2 )))
#將小圖像粘貼到大圖像
for index in range(len(images)):
#計算要粘貼到的行
row = int(index / n)
#計算粘貼到的列
col = index - n * row
#粘貼
grid_img.paste(images[index], (col * width, row * height))
return grid_img
def main():
#打開圖形
target_image = Image.open(target_image1)
#分割圖形
target_images =splitImage(target_image,[128, 128])
#粘貼新圖形
mosaic_image = createImageGrid(target_images, [128, 128])
#保存
output_filename = 'mosaic1.png'
#mosaic_image.save(output_filename)
mosaic_image.save('D:\python項目\\照片馬賽克\\masaka1.png')
if __name__ == '__main__':
main()
原圖:
塊狀處理後效果: