matplotlib 来生成图片 subplot_mosaic Leif Denby:用了十多年Python,才发现plt.subplot_mosaic(…),绝对是matplotlib里最牛x的函数
算法具体描述
- 将瓦片图转换为灰度并读入tiles矩阵,得tiles[2000x10x10]
- 将目标图像读入target矩阵,得target[1x600x800]
- 根据tiles的大小求出图标图像被分割的网格数
- 创建随机排列矩阵ranges(),定义期望值sam,
- 求出相似度矩阵similar。循环:随机选取一个瓦片图像除以目标图像的一个网格图,结果矩阵求和,和与期望sam的比值与1求差,再取绝对值,存入similar(i , j)
- similar矩阵求和,得出总相似度result
- 创建随机数temp(0-2000),height,width
- 当总相似度大于设定值时,则执行:随机用某瓦片和某网格做相似度匹配,得出相似度数final,如果final小于原来similar固定位置的数值,则用final替换similar中的值,并更新随机排列矩阵ranges(),如果final不小于则不更改
- 8中的循环每隔三百次就用ranges()矩阵中记录的值来合成图像,然后将图像输出并保存。
function RandomSearchMosaic( image, avail, error, pool, output )
target(1,:,:) = rgb2gray(imread(image));
for i=1:avail
tiles(i,:,:) = rgb2gray(imread([ pool, '\tile (' num2str(i) ').jpg']));
end
sz = size(target);
row = sz(2)/10;
col = sz(3)/10;
sam = 100 ;
// avail 是pool中的图片的数量
ranges(:,:) = fix(rand(row,col)*avail)+1;
for i=1:row
for j=1:col
degree=sum(sum(tiles(ranges(i,j),:,:)./target(1,((i-1)*10)+1:((i-1)*10)+10,((j-1)*10)+1:((j-1)*10)+10)));
similar(i,j) = abs((degree/sam) - 1);
end
end
out = 1;
cout = 1;
result = sum(sum(similar));
while result >= error
height= fix(rand(1)*row)+1;
width = fix(rand(1)*col)+1;
for i=1:2000
temp = fix(rand(1)*avail)+1;
degree_s=sum(sum(tiles(temp,:,:)./target(1,((height-1)*10)+1:((height-1)*10)+10,((width-1)*10)+1:((width-1)*10)+10)));
final= abs((degree_s/sam) - 1);
if final < similar(height,width)
ranges(height,width) = temp;
similar(height,width) = final;
break;
end
end
if mod(cout,500) == 0
img = getImage(col,row,ranges,tiles);
result = sum(sum(similar));
display(result);
imwrite(img,[output,'\out_img', num2str(out) '.jpg'],'jpg');
out = out + 1;
end
cout = cout + 1;
End
% getImage函数根据tiles矩阵的数值定位每一幅瓦片图像在网格中的位置,得到合成图像
function img=getImage(width,height,arr,tiles)
for i=1:height
for j=1:width
img(((i-1)*10)+1:((i-1)*10)+10,((j-1)*10)+1:((j-1)*10)+10) = tiles(arr(i,j),:,:);
end
end
python代码实现
ai写的,我没测试过下面的代码
import numpy as np
import cv2
def random_search_mosaic(image, avail, error, pool, output):
# 1. 将瓦片图像转换为灰度图并读入tiles矩阵
target = cv2.imread(image)
target_gray = cv2.cvtColor(target, cv2.COLOR_BGR2GRAY)
tiles = []
for i in range(1, avail+1):
tile = cv2.imread(f"{pool}/tile ({i}).jpg")
tiles.append(cv2.cvtColor(tile, cv2.COLOR_BGR2GRAY))
tiles = np.array(tiles)
# 2. 计算图像被分割的网格数
h, w = target_gray.shape
row, col = h // 10, w // 10
# 3. 创建随机排列的ranges矩阵
sam = 100
ranges = np.random.randint(1, avail+1, size=(row, col))
# 4. 计算每个网格与对应瓦片图像之间的相似度
similar = np.zeros((row, col))
for i in range(row):
for j in range(col):
degree = np.sum(tiles[ranges[i,j]-1] / target_gray[i*10:(i+1)*10, j*10:(j+1)*10])
similar[i,j] = abs((degree/sam) - 1)
# 5. 计算overall的相似度
result = np.sum(similar)
# 6. 进行迭代优化
out, cout = 1, 1
while result >= error:
height, width = np.random.randint(0, row), np.random.randint(0, col)
for _ in range(2000):
temp = np.random.randint(1, avail+1)
degree_s = np.sum(tiles[temp-1] / target_gray[height*10:(height+1)*10, width*10:(width+1)*10])
final = abs((degree_s/sam) - 1)
if final < similar[height, width]:
ranges[height, width] = temp
similar[height, width] = final
break
if cout % 500 == 0:
img = get_image(col, row, ranges, tiles)
result = np.sum(similar)
print(result)
cv2.imwrite(f"{output}/out_img{out}.jpg", img)
out += 1
cout += 1
def get_image(width, height, arr, tiles):
img = np.zeros((height*10, width*10), dtype=np.uint8)
for i in range(height):
for j in range(width):
img[i*10:(i+1)*10, j*10:(j+1)*10] = tiles[arr[i,j]-1]
return img
另一个版本的代码
from PIL import Image
import os
import random
# 定义瓦片图的尺寸
TILE_SIZE = 32
# 获取目录下的所有瓦片图
def get_tiles(path):
tiles = []
for file in os.listdir(path):
file_path = os.path.join(path, file)
if os.path.isfile(file_path):
try:
with Image.open(file_path) as tile:
if tile.size == (TILE_SIZE, TILE_SIZE):
tiles.append(tile)
except:
pass
return tiles
# 将主图切割成若干块小图
def get_blocks(image):
width, height = image.size
blocks = []
for y in range(0, height, TILE_SIZE):
for x in range(0, width, TILE_SIZE):
box = (x, y, x + TILE_SIZE, y + TILE_SIZE)
blocks.append(image.crop(box))
return blocks
# 获取小图和目标颜色的差异度
def color_distance(c1, c2):
r1, g1, b1 = c1
r2, g2, b2 = c2
return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2
# 从瓦片图列表中找出最匹配目标颜色的瓦片图,并返回其索引和差异度
def find_best_match(tiles, target_color):
best_index = 0
best_distance = color_distance(tiles[0].getpixel((0, 0)), target_color)
for i, tile in enumerate(tiles[1:]):
distance = color_distance(tile.getpixel((0, 0)), target_color)
if distance < best_distance:
best_index = i + 1
best_distance = distance
return best_index, best_distance
# 生成马赛克
def create_mosaic(image, tiles):
# 将主图切割成若干块小图
blocks = get_blocks(image)
# 创建和主图大小相同的画布
mosaic = Image.new('RGB', image.size)
# 遍历每个小图,选取和其颜色最匹配的瓦片图来填充
for i, block in enumerate(blocks):
print(f'\rProcessing block {i+1} of {len(blocks)}', end='')
target_color = block.getpixel((TILE_SIZE//2, TILE_SIZE//2))
best_index, _ = find_best_match(tiles, target_color)
mosaic.paste(tiles[best_index], block.getbbox())
return mosaic
if __name__ == '__main__':
# 加载主图
with Image.open('taj_mahal.jpg') as image:
# 获取瓦片图
tiles = get_tiles('tiles')
# 生成马赛克
mosaic = create_mosaic(image, tiles)
mosaic.show()
还有一个版本的
"""
mosaic.py
A simple python script that creates a mosaic from an input image.
Dependencies:
Python Imaging Library, available at http://www.pythonware.com/products/pil/
Summary of methods:
print_fn(s) prints s to the terminal only when the verbose option is selected
gcd(a,b) returns the greatest common divisor of a and b
max_color(img) returns the most frequent color of img
average_value(img) returns the average R,G,B values of an img
square_crop(img) crops img into the largest possible square; the crop is centered
center_crop(img, resolution) crops img into the largest possible rectangle with aspect ratio resolution; the crop is centered
build_chest(directory) returns a computed dictionary with key=feature of img and val = directory location of img
nearest_neighbor(img, directory) finds the image in directory most similar to img based on feature
vector_error(v,u) returns the linear difference (sum) of two vectors u and v
mosaic(input_image, image_stash, resolution, thumbnail_size, func, num_of_images) does the magic
cleanup(input_image, directory) cleans up after the magic
Copyright (c) 2010, Sameep Tandon
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Sameep Tandon nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Sameep Tandon BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
I feel legit using a Berkeley Software License. GO BEARS!
"""
import Image
import math
import os
import sys
inf = 1e1000
verbose = False
def print_fn( s ):
"""
prints diagnostic messages if the verbose option has been enabled
@s: string s to print
"""
global verbose
if verbose:
print s
def gcd(a,b):
"""
Returns the greatest common divisor of two numbers, a and b
@a: input number 1
@b: input number 2
"""
while b != 0:
a, b = b, a%b
return a
def max_color (img):
"""
Returns the most frequent color used in the img
@img: An image object
"""
return max( img.getcolors(img.size[0] * img.size[1]) )[1]
def average_value (img, lowerbound=(0,0), upperbound=None):
"""
Returns the average (R,G,B) of the image
@img: an Image object
@lowerbound: optional parameter; (min_x, min_y) tuple of pixels
@upperbound: optional parameter; (max_x, max_y) tuple of pixels
"""
min_x, min_y = lowerbound
if upperbound == None:
upperbound = img.size
max_x, max_y = upperbound
img_width, img_height = img.size
if min_x < 0 or min_y < 0 or max_x > img_width or max_y > img_height or max_x < min_x or max_y < min_y:
print_fn ("Warning: Bad input; dumping data below.")
print_fn ("img_width, img_height = " + str(img.size))
print_fn ("min_x, min_y = " + str(lowerbound))
print_fn ("max_x, max_y = " + str(upperbound))
sys.exit(2)
return None
r = 0
g = 0
b = 0
count = 0
pix = img.load()
for x in range(min_x, max_x):
for y in range(min_y, max_y):
temp_r, temp_g, temp_b = pix[x,y]
r += temp_r
g += temp_g
b += temp_b
count += 1
return ( float(r) / float(count), float(g) / float(count), float(b) / float(count) )
def square_crop (img):
"""
Returns a square crop (with square at center) of an image
@img: the input img file
"""
return center_crop(img, (1,1))
def center_crop (img, resolution, constrained_aspect_ratio=1):
"""
Returns an image file that has been cropped to be proportional to the resolution such that the crop is done in
the center
@img: the input img file
@resolution: a tuple (x,y) for which the resulting image will be proportional to
@constrained_aspect_ratio: an indicator stating whether the aspect ratio OF THE RESOLUTION must be constrained
"""
try:
im = Image.open(img)
except IOError:
print_fn ("Could not open input image file at " + img)
raise IOError
x,y = ( resolution[0] / gcd(resolution[0], resolution[1]), resolution[1] / gcd(resolution[0],resolution[1]) )
im_width, im_height = im.size
box_x = 0
box_y = 0
if constrained_aspect_ratio:
while box_x+x <= im_width and box_y+y <= im_height:
box_x += x
box_y += y
width_to_crop = im_width - box_x
height_to_crop = im_height - box_y
else:
width_to_crop = im_width % resolution[0]
height_to_crop = im_height % resolution[1]
im = im.crop( (width_to_crop / 2 + (width_to_crop % 2), height_to_crop / 2 + (height_to_crop % 2), im_width - width_to_crop / 2, im_height - height_to_crop / 2) )
return im
def build_chest(directory, chest, func=max_color, thumbnail_size=(50,50), num_of_images=None):
"""
Returns a dictionary with key = func(img), value = img
@directory: a directory of images to build the mosaic out of
@func: optional parameter; classification method used. average_value or max_color are viable choices
@thumbnail_size: size of each image in the mosaic
@num_of_images: max number of images to use
"""
targetDir = directory + "/"
tmpDir = targetDir.split('/')[0] + "/temp/"
for file in os.listdir(targetDir):
if os.path.isdir(targetDir + "/" + file):
build_chest(targetDir + "/" + file, chest, func, thumbnail_size, num_of_images)
if not os.path.isdir(tmpDir):
os.mkdir(tmpDir)
if num_of_images == None:
num_of_images = len(os.listdir(targetDir))
for file in os.listdir(targetDir):
if not os.path.isdir(targetDir + "/" + file):
try:
im_transform = center_crop(targetDir + file, thumbnail_size)
im_transform.thumbnail(thumbnail_size, Image.ANTIALIAS)
print_fn ("Creating file " + tmpDir + os.path.splitext(file)[0] + ".thumbnail.jpg")
im_transform.save(tmpDir + os.path.splitext(file)[0] + ".thumbnail.jpg", "JPEG")
im_transform = Image.open(tmpDir + os.path.splitext(file)[0] + ".thumbnail.jpg")
key = func(im_transform)
chest[key] = tmpDir + os.path.splitext(file)[0] + ".thumbnail.jpg"
num_of_images -= 1
except IOError:
print_fn (file + " is not an image file; Skipping it")
return chest
def nearest_neighbor(img, chest, func=max_color, chest_keys=None):
"""
Returns an image in the chest that is closest in color to img
@img: img to classify
@chest: a dictionary with key = func(img), value = img
@func: optional parameter; classification method used. average_value or max_color are viable choices
@chest_keys: optional parameter; all the keys of chest, used to save computation time
"""
min_error = inf
argmin_error = None
img_val = func(img)
min_key = None
for key in chest.keys():
if vector_error(img_val, key) < min_error:
min_error = vector_error(img_val, key)
argmin_error = chest[key]
min_key = key
del chest[min_key]
return argmin_error
def vector_error (v, u):
"""
Returns the magnitude of the difference vector
@v: vector v, represented as an iterable object in order (tuple or list)
@u: vector u, same length as v
"""
diff = list(v)
error = 0
for i in range(0,len(v)):
diff[i] -= u[i]
error += math.fabs(diff[i])
return error
def mosaic (input_image, image_stash, resolution=(25,25), thumbnail_size=(50,50), func=average_value, num_of_images=None):
"""
Saves an image file that is a mosaic of input_image.
@input_image: the location of the input image
@image_stash: the directory of all the possible images to put in the mosaic
@resolution: a tuple (x,y) of size rectangles to break the input image into
@thumbnail_size: the size of each thumbnail image in the mosaic
@func: the classifier function to use
@num_of_images: the number of images to use in the mosaic
"""
im = center_crop (input_image, resolution, False)
chest = { }
build_chest(image_stash, chest, func, thumbnail_size, num_of_images)
chest_keys = chest.keys( )
im_width, im_height = im.size
im.save(os.getcwd() + "/" + os.path.splitext(input_image)[0] + ".tmp.jpg", "JPEG")
im = Image.open(os.getcwd() + "/" + os.path.splitext(input_image)[0] + ".tmp.jpg")
mos_size = ((im_width / resolution[0]) * thumbnail_size[0], (im_height / resolution[1]) * thumbnail_size[1])
mos = Image.new(im.mode, mos_size, (30, 20, 255))
for x in range( im_width / resolution[0] ):
print_fn (str(x+1) + " of " + str( im_width / resolution[0] ) + " columns")
for y in range (im_height / resolution[1] ):
start_x = x * resolution[0]
start_y = y * resolution[1]
end_x = start_x + resolution[0]
end_y = start_y + resolution[1]
box = (start_x, start_y, end_x, end_y)
query = im.transform(resolution, Image.EXTENT,box)
reply = Image.open( nearest_neighbor(query, chest, func, chest_keys) )
start_x = x * thumbnail_size[0]
start_y = y * thumbnail_size[1]
end_x = start_x + thumbnail_size[0]
end_y = start_y + thumbnail_size[1]
box = (start_x, start_y, end_x, end_y)
mos.paste(reply, box)
mos.save(os.getcwd() + "/" + os.path.splitext(input_image)[0] + ".mosaic.jpg", "JPEG")
cleanup( input_image, image_stash )
def cleanup(input_image, directory):
"""
Cleans up the mess
@input_image: filename of the input image
@directory: the directory where the mess was made
"""
print_fn ("Initializing cleanup procedure. Deleting tmp files")
targetDir = directory + "/"
tmpDir = targetDir + "temp/"
os.remove(os.getcwd() + "/" + os.path.splitext(input_image)[0] + ".tmp.jpg")
for file in os.listdir(tmpDir):
print_fn ("Removing file " + tmpDir + file)
os.remove(tmpDir + file)
os.removedirs(tmpDir)
print_fn ("Cleanup complete.")
def main():
from optparse import OptionParser
usage = "usage: %prog -i [input image] -s [directory of images] -r [x] [y] -t [x] [y]\n"
usage += "Optional arguments -n, -a, -v"
parser = OptionParser(usage=usage)
parser.add_option("-i", "--input", dest="input_image", help="Input Image File")
parser.add_option("-s", "--stash", dest="image_stash", help="Directory of images")
parser.add_option("-r", "--resolution", dest="resolution", help="Size of tile to inspect at in input image", nargs=2)
parser.add_option("-t", "--thumbnail", dest="thumbnail", help="Size of tile to write in output image", nargs=2)
parser.add_option("-n", "--numImages", dest="number_of_images", help="Number of images to look at in stash")
parser.add_option("-a", "--averageValue", dest="func", help="Average Value Classifier; instead of default MAX_COLOR. Average Value is better, but slower", action="store_true")
parser.add_option("-v", "--verbose", dest="verbose", help="Verbose option; Prints diagnostic messages", action="store_true")
(options, args) = parser.parse_args()
if not options.input_image or not options.image_stash or not options.resolution or not options.thumbnail or not len(options.resolution)==2 or not len(options.thumbnail) == 2:
print "Incorrect Usage; please see python mosaic.py --help"
sys.exit(2)
if options.verbose:
global verbose
verbose = True
try:
input_image = options.input_image
image_stash = options.image_stash
resolution = ( int(options.resolution[0]), int(options.resolution[1]) )
thumbnail_size = ( int(options.thumbnail[0]), int(options.thumbnail[1]) )
func = max_color
if options.func:
func = average_value
except:
print "Incorrect Usage; please see python mosaic.py --help"
sys.exit(2)
if options.number_of_images:
number_of_images = int(options.number_of_images)
mosaic( input_image, image_stash, resolution, thumbnail_size, func, number_of_images )
else:
mosaic( input_image, image_stash, resolution, thumbnail_size, func)
sys.exit()
if __name__ == "__main__":
main()
第三版
import os
import numpy as np
from PIL import Image
import cv2
def makeColorMosaicByRII(target, avail, error, MAXITR, MAXREP, pool, output):
"""
使用RII算法生成彩色马赛克视频
参数:
target (str): 目标图像文件名
avail (int): 可用图像块的数量
error (int): 最大允许误差,一般设置为100000左右
MAXITR (int): 程序运行的最大迭代次数
MAXREP (int): 每个图像块最大重复次数
pool (str): 图像块所在目录
output (str): 输出视频文件保存路径
返回:
aviobj (cv2.VideoWriter): 输出视频对象
"""
# 读取目标图像
target_img = np.array(Image.open(target))
# 获取目标图像尺寸和图块数量
h, w, _ = target_img.shape
tile_h, tile_w = 10, 10
n_row, n_col = h // tile_h, w // tile_w
# 加载图像块到内存
img_db = []
for i in range(1, avail + 1):
img_path = os.path.join(pool, f'tile ({i}).jpg')
img_db.append(np.array(Image.open(img_path).resize((tile_h, tile_w))))
# 初始化马赛克
mosaic = np.zeros((n_row, n_col), dtype=int)
# 计算每个图块与目标区域的差异,存储在 tilediff 矩阵中
tilediff = np.zeros((n_row, n_col))
fit = 0
for j in range(n_row):
for k in range(n_col):
# 随机选择一个图块
n = np.random.randint(1, avail + 1)
# 确保选择的图块没有超过 MAXREP 次重复
while len(np.where(mosaic == n)[0]) >= MAXREP:
n = np.random.randint(1, avail + 1)
mosaic[j, k] = n
# 计算该图块与目标区域的差异,并更新总误差 fit
tile_img = img_db[n - 1]
tilediff[j, k] = np.sum(np.abs(tile_img - target_img[j*tile_h:(j+1)*tile_h, k*tile_w:(k+1)*tile_w]))
fit += tilediff[j, k]
# 记录当前最优马赛克及其误差
best = mosaic.copy()
bfit = fit
# 迭代优化
itr, out = 1, 1
while itr <= MAXITR and bfit > error:
# 邻居1:随机替换一个图块
j, k = np.random.randint(0, n_row), np.random.randint(0, n_col)
n = np.random.randint(1, avail + 1)
while len(np.where(mosaic == n)[0]) >= MAXREP:
n = np.random.randint(1, avail + 1)
neigh1 = mosaic.copy()
neigh1[j, k] = n
ndiff1 = tilediff.copy()
ndiff1[j, k] = np.sum(np.abs(img_db[n-1] - target_img[j*tile_h:(j+1)*tile_h, k*tile_w:(k+1)*tile_w]))
f1 = fit - tilediff[j, k] + ndiff1[j, k]
# 邻居2:随机交换两个图块
j1, k1 = np.random.randint(0, n_row), np.random.randint(0, n_col)
j2, k2 = np.random.randint(0, n_row), np.random.randint(0, n_col)
neigh2 = mosaic.copy()
neigh2[j1, k1], neigh2[j2, k2] = neigh2[j2, k2], neigh2[j1, k1]
ndiff2 = tilediff.copy()
ndiff2[j1, k1] = np.sum(np.abs(img_db[neigh2[j1, k1]-1] - target_img[j1*tile_h:(j1+1)*tile_h, k1*tile_w:(k1+1)*tile_w]))
ndiff2[j2, k2] = np.sum(np.abs(img_db[neigh2[j2, k2]-1] - target_img[j2*tile_h:(j2+1)*tile_h, k2*tile_w:(k2+1)*tile_w]))
f2 = fit - tilediff[j1, k1] - tilediff[j2, k2] + ndiff2[j1, k1] + ndiff2[j2, k2]
# 选择更优的邻居
if f1 < f2:
neigh, ndiff, f = neigh1, ndiff1, f1
else:
neigh, ndiff, f = neigh2, ndiff2, f2
# 更新最优解
if f <= bfit:
mosaic, best, tilediff, bfit, fit = neigh, neigh, ndiff, f, f
else:
diff = f - fit
if np.random.rand() < np.exp(-diff / 10):
mosaic, tilediff, fit = neigh, ndiff, f
# 每 200 次迭代保存一次中间结果
if itr % 200 == 0:
print(f'Iteration: {itr}, Fitness: {bfit}')
img = getRIIImage(best, img_db)
Image.fromarray(img).save(os.path.join(output, f'result{out}.jpg'))
out += 1
itr += 1
# 生成视频
return makeColorMovie(output, output, out-1, 'result', 'jpg')
def getRIIImage(mosaic, img_db):
"""
根据马赛克信息生成图像
"""
h, w = mosaic.shape
tile_h, tile_w = 10, 10
img = np.zeros((h*tile_h, w*tile_w, 3), dtype=np.uint8)
for j in range(h):
for k in range(w):
img[j*tile_h:(j+1)*tile_h, k*tile_w:(k+1)*tile_w] = img_db[mosaic[j, k]-1]
return img
def makeColorMovie(output_dir, filename_prefix, num_frames, filename_suffix, file_format):
"""
根据中间结果生成视频
参数:
output_dir (str): 输出视频文件保存路径
filename_prefix (str): 中间结果文件名前缀
num_frames (int): 中间结果文件数量
filename_suffix (str): 中间结果文件名后缀
file_format (str): 中间结果文件格式
返回:
video_writer (cv2.VideoWriter): 输出视频对象
"""
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
fps = 5
size = (10*10, 10*10)
video_writer = cv2.VideoWriter(os.path.join(output_dir, 'result.mp4'), fourcc, fps, size)
for i in range(1, num_frames+1):
img_path = os.path.join(output_dir, f"{filename_prefix}{i}.{file_format}")
frame = cv2.imread(img_path)
video_writer.write(frame)
video_writer.release()
return video_writer
第四版
import os
import cv2
import numpy as np
from tqdm import tqdm
def make_gray_mosaic_by_random_search(target, avail, error, max_itr, pool, output):
"""
创建一个视频,目标图像逐步演化。
参数:
target (str): 目标图像的文件名。目标图像的两个维度必须是10的倍数。
avail (int): 池文件夹中可用的图像块数量。
error (int): 允许的最大误差,实际值约为100000。
max_itr (int): 程序应运行的最大迭代次数,然后制作视频。
pool (str): 定义池文件夹的目录。
output (str): 定义结果视频存储的位置。
"""
# 初始化计数器
itr = 1
out = 1
# 读取目标图像
tgt_pic = cv2.cvtColor(cv2.imread(target), cv2.COLOR_BGR2GRAY)
# 将图像块读入内存
tiles = [cv2.cvtColor(cv2.imread(os.path.join(pool, f"tile ({i+1}).jpg")), cv2.COLOR_BGR2GRAY) for i in range(avail)]
# 确定目标图像的尺寸
height, width = tgt_pic.shape
height //= 10
width //= 10
# 使用池中的随机元素初始化搜索
climber = np.random.randint(1, avail+1, (height, width), dtype=int)
# 计算当前图像和目标图像之间的差异
g_diff = np.array([
[np.sum(np.abs(tiles[climber[i, j]-1] - tgt_pic[i*10:(i+1)*10, j*10:(j+1)*10])) for j in range(width)]
for i in range(height)
])
# 计算适应度
fitness = np.sum(g_diff)
print(fitness)
# 程序的主循环
pbar = tqdm(total=max_itr, desc="Processing")
while fitness >= error and itr < max_itr:
row = np.random.randint(0, height)
col = np.random.randint(0, width)
for _ in range(2501):
temp = np.random.randint(0, avail)
t_diff = np.sum(np.abs(tiles[temp] - tgt_pic[row*10:(row+1)*10, col*10:(col+1)*10]))
if t_diff < g_diff[row, col]:
climber[row, col] = temp + 1
g_diff[row, col] = t_diff
break
fitness = np.sum(g_diff)
img = get_image(width, height, climber, tiles)
if itr % 200 == 0:
print(fitness)
cv2.imwrite(os.path.join(output, f"result{out}.jpg"), img)
out += 1
itr += 1
pbar.update(1)
pbar.close()
# 创建视频
make_gray_movie(output, output, out-1, "result", "jpg")
def get_image(width, height, climber, tiles):
"""
根据瓷砖索引生成图像。
"""
img = np.zeros((height*10, width*10), dtype=np.uint8)
for i in range(height):
for j in range(width):
img[i*10:(i+1)*10, j*10:(j+1)*10] = tiles[climber[i, j]-1]
return img
def make_gray_movie(output_dir, output_name, num_frames, frame_name, frame_ext):
"""
从单个图像创建视频。
"""
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out_video = cv2.VideoWriter(os.path.join(output_dir, f"{output_name}.mp4"), fourcc, 24, (640, 480))
for i in range(1, num_frames+1):
frame = cv2.imread(os.path.join(output_dir, f"{frame_name}{i}.{frame_ext}"))
out_video.write(frame)
out_video.release()