使用 Python 創建照片馬賽克(實驗樓實驗),源代碼已經包含課後作業。

- 最近在實驗樓做的小項目,這個項目主要用的庫有argparse(命令行解析工具),PIL(python標準圖像庫),os(文件處理)。

- 說下基本原理:簡單來說就是圖片馬賽克化處理,處理效果就是圖片像打了馬賽克一樣;

- 我們將目標圖像劃分成較小圖像的網格,並用適當的圖像替換網格中的每一小塊,創建原始圖像的照片馬賽克。你可以指定網格的尺寸,並選擇輸入圖像是否可以在馬賽克中重複使用。

實驗思路

在這裏插入圖片描述

整體思路很簡單,源代碼中函數比較多,代碼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()

原圖:
在這裏插入圖片描述
塊狀處理後效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章