用numpy和PIL生成马赛克

照片上生成马赛克

其实生成马赛克的方法有很多种,最简单的方法是用Opencv来生成,用Opencv生成马赛克的方法会在文末列出,只有几行代码。
这里我们用numpy和PIL(Python图像库),主要是学习编程思路和几个关键方法的应用:
比如以下方法:

  • Image.paste()
  • Image.fromarray()
  • splitArray[:,:,:] = avg # 以指定颜色值填充整个数组, avg=(R,G,B)
  • argsparser参数输入

编程思路:

  1. 读入目标图像,用 --input-image来指定
  2. 将目标图片分割成MxN个小块,用–grid-size来指定
  3. 对于每个小块的R,G,B单独求平均值
  4. 将RGB平均值填充到每个小块
  5. 将填充后的小块放到对应的位置

以下是实现的代码,重要的位置用中文注释

import sys, os, random, argparse
from PIL import Image
import numpy as np


def getAverageRGB(image):
    """
    return the average RGB value of an image
    """
    # 将图像转换为numpy数组
    im = np.array(image)
    # 得到每个输入图像的形状
    w, h, d = im.shape
    # 求平均值
    ava_color = np.trunc((np.average(im.reshape(w * h, d), axis=0)))
    # 转换为元组,元组中的元素为整数
    ava_color = tuple(int(item) for item in ava_color)
    return ava_color


def splitImage(image, size):
    """
    given the image and dimensions(rows,cols), return an m*n list of images
    """
    W, H = image.size[0], image.size[1]
    m, n = size
    w, h = int(W / n), int(H / m)
    # 图像列表
    imgs = []
    for j in range(m):
        for i in range(n):
            # 添加裁剪后的图像到列表
            imgs.append(image.crop((i * w, j * h, (i + 1) * w, (j + 1) * h)))
    return imgs


def getArray(img_shape, avg_color):
    """
    get a numpy array with average RGB value
    """

    avg = avg_color  # 输入的图像颜色值
    # 创建一个指定形状和类型的数组
    splitArray = np.zeros((img_shape[0], img_shape[1], 3), dtype=np.uint8)
    splitArray[:, :, :] = avg  # 以指定颜色值填充整个数组
    return splitArray


def creatImageGrid(images, dims):
    """
    given a list of images and a grid size(m,n), return a new image
    """
    m, n = dims
    # assert断言,如果条件为假,则终止程序
    assert m * n == len(images)
    # 得到第一张图像的宽度和高度
    width = images[0].shape[0]
    height = images[0].shape[1]
    # 这里不假设所有图像的大小相同,所以需要遍历所有图像,找到最大的宽度和高度,我们这里是相同的,
    # 为了方便,我们可以假设所有图像的大小相同,注释掉下面两行代码
    # width = max([img.shape[0] for img in images])
    # height = max([img.shape[1] for img in images])
    # 创建一个新的图像,模式为RGB,大小为n*width*m*height
    grid_image = Image.new('RGB', (n * width, m * height))
    for j in range(m):
        for i in range(n):
            # 粘贴图像到ixj网格的指定位置,左上角坐标为(i * width, j * height),Image.fromarray()将numpy数组转换为PIL图像
            grid_image.paste(Image.fromarray(images[j * n + i]), (i * width, j * height))
    grid_image.show()
    return grid_image


def createPhotomosaic(target_image, grid_size):
    """
    create photomosaic given the target image and grid size
    """
    print("正在分割输入的图像...")
    # 分割图像
    target_images = splitImage(target_image, grid_size)
    print("正在生成输出图像...")
    output_images = []
    # 计数器
    count = 0
    batch_size = int(len(target_images) / 10)

    for img in target_images:
        # 计算每个图像的平均RGB值
        avg_color = getAverageRGB(img)
        img = np.array(img)  # 将PIL图像转换为numpy数组
        array = getArray(img.shape, avg_color)  # 生成指定形状和颜色的numpy数组
        output_images.append(array)  # 将生成的numpy数组添加到输出图像列表中
        # 用户反馈
        if count > 0 and batch_size > 10 and count % batch_size == 0:  # 每处理10%的图像,打印一次进度
            print('完成 %d 之 %d...' % (count, len(target_images)))
        count += 1

    print('正在生成马赛克...')
    # 创建马赛克图像
    mosaic_image = creatImageGrid(output_images, grid_size)

    # 显示图像
    return mosaic_image


# main 函数
def main():
    # command line arguments are in sys.argv[1], sys.argv[2] ...
    # sys.argv[0] is the name of the script itself and can be ignored
    # 创建参数解析器
    parser = argparse.ArgumentParser(description='Create a photomosaic.')
    # 添加参数
    parser.add_argument('--target-image', dest='targetImage', required=True)
    parser.add_argument('--grid-size', nargs=2, dest='grid_size', required=True)
    parser.add_argument('--output-file', dest='output_file', required=False)
    # 解析参数
    args = parser.parse_args()

    ###### INPUTS ######
    # 目标图像
    target_image = Image.open(args.targetImage)

    # 网格大小
    grid_size = (int(args.grid_size[0]), int(args.grid_size[1]))

    # 输出图像
    output_filename = 'mosaic.png'
    if args.output_file:
        output_filename = args.output_file

    ###### END INPUTS ######

    print('开始创建马赛克图像...')

    # 调用函数创建马赛克图像
    mosaic_image = createPhotomosaic(target_image, grid_size)
    # 保存图像
    mosaic_image.save(output_filename, 'PNG')
    print('保存图像至 %s' % output_filename)
    print('完成')


# 调用main函数
if __name__ == '__main__':
    main()

运行实例

原始图像
用numpy和PIL生成马赛克_第1张图片
输入以下命令:

 python .\newphotomosaic.py --target-image .\R-C.jpg --grid-size 100 100

结果如下:
用numpy和PIL生成马赛克_第2张图片
如果用Opencv则简单很多:

import cv2 as cv
import sys
import numpy as np

if __name__ == '__main__':
    img = cv.imread('R-C.jpg')
    if img is None:
        print('读取图片失败')
        sys.exit()
    else:
        print('原始图像的形状:{}\n元素数据类型:{}\n图像通道数:{}\n像素总数:{}'
              .format(img.shape, img.dtype, img.ndim, img.size))
    # 缩小图像,插值方法为双线性插值
    mosaic_m=50 #水平方向上的马赛克像素数
    mosaic_n=100 #垂直方向上的马赛克像素数
    # 缩小图像,插值方法为双线性插值
    small_image = cv.resize(img, (mosaic_m, mosaic_n), interpolation=cv.INTER_LINEAR)
    img1 = np.repeat(small_image, int(img.shape[0]/mosaic_n), axis=0) # 沿着列(垂直)方向重复,高度尺寸接近原图像
    mosaic_image = np.repeat(img1, int(img.shape[1]/mosaic_m), axis=1) # 沿着行(水平)方向重复,宽度尺寸接近原图像
    print('放大图像的形状:{}\n元素数据类型:{}\n图像通道数:{}\n像素总数:{}'
          .format(mosaic_image.shape, mosaic_image.dtype, mosaic_image.ndim, mosaic_image.size))
    cv.namedWindow('mosaic1', cv.WINDOW_AUTOSIZE)
    cv.imshow('mosaic1', mosaic_image)
    cv.imwrite('mosaic1.jpg', mosaic_image)
    cv.waitKey(0)
    cv.destroyAllWindows()

你可能感兴趣的:(Python,学习,numpy,python,opencv)