使用PIL通过在RGB元组中插入灰度值将灰度图像插入RGB图像

时间:2013-05-10 02:35:26

标签: python python-imaging-library

我正在编写几个函数,第一个将灰度位图图像插入到另一个颜色位图图像中。现在我的目标基本上是获取灰度像素图像的每个数字(例如123)并替换每个RGB像素(244,244,244)的结束数字,所以它基本上会像这样结束(241,242,243) )。基本上,这是使用灰度图像为彩色图像加水印。

以下代码是我到目前为止,我能够在列表中返回元组值,我只是不知道如何在较大的图像中操作较小灰度图像大小的空间。

def add_watermark():
    image = Image.open()
    pixels = list(image.getdata())
    image.putdata()
    image.save()

    for i in range(img.size[0]):
        for j in range(img.size[1]):
            pixels[i,j] = (i, j, 100)

有人可以提供一些建议吗?

2 个答案:

答案 0 :(得分:0)

你走在正确的轨道上。这就是你操纵像素的方法,尽管你可以使用pixel access objects更快一点,如下所示。

除了提取和设置正确的数字外,这一切都非常简单。在这个例子中,我已经通过除以10的幂并使用模运算符来完成,尽管还有其他方法。希望这些评论能够很好地解释它。

from PIL import Image

def add_watermark(watermark_path, image_in_path, image_out_path):
    # Load watermark and image and check sizes and modes
    watermark = Image.open(watermark_path)
    assert watermark.mode == 'L'
    image = Image.open(image_in_path)
    assert image.mode == 'RGB'
    assert watermark.size == image.size

    # Get pixel access objects
    watermark_pixels = watermark.load()
    image_pixels = image.load()

    # Watermark each pixel
    for x in range(image.size[0]):
        for y in xrange(image.size[1]):
            # Get the tuple of rgb values and convert to a list (mutable)
            rgb = list(image_pixels[x, y])
            for i, p in enumerate(rgb):
                # Divide the watermark pixel by 100 (r), then 10 (g), then 1 (b)
                # Then take it modulo 10 to get the last digit
                watermark_digit = (watermark_pixels[x, y] / (10 ** (2 - i))) % 10
                # Divide and multiply value by 10 to zero the last digit
                # Then add the watermark digit
                rgb[i] = (p / 10) * 10 + watermark_digit
            # Convert back to a tuple and store in the image
            image_pixels[x, y] = tuple(rgb)

    # Save the image
    image.save(image_out_path)

答案 1 :(得分:0)

如果您对图像水印感兴趣,可能需要查看steganography。例如,Digital_Sight是该概念的工作演示,可用作存储用作水印的文本的基础。要研究如何修改图像中的各种像素位可以改变其质量,您可能希望在决定要覆盖哪些数据之前使用Color_Disruptor


<强> Digital_Sight

import cStringIO
from PIL import Image
import bz2
import math

################################################################################

PIXELS_PER_BLOCK = 4
BYTES_PER_BLOCK = 3
MAX_DATA_BYTES = 16777215

################################################################################

class ByteWriter:

    "ByteWriter(image) -> ByteWriter instance"

    def __init__(self, image):
        "Initalize the ByteWriter's internal variables."
        self.__width, self.__height = image.size
        self.__space = bytes_in_image(image)
        self.__pixels = image.load()

    def write(self, text):
        "Compress and write the text to the image's pixels."
        data = bz2.compress(text)
        compressed_size = len(data)
        if compressed_size > self.__space:
            raise MemoryError('There is not enough space for the data!')
        size_data = self.__encode_size(compressed_size)
        tail = '\0' * ((3 - compressed_size) % 3)
        buffer = size_data + data + tail
        self.__write_buffer(buffer)

    @staticmethod
    def __encode_size(number):
        "Convert number into a 3-byte block for writing."
        data = ''
        for _ in range(3):
            number, lower = divmod(number, 256)
            data = chr(lower) + data
        return data

    def __write_buffer(self, buffer):
        "Write the buffer to the image in blocks."
        addr_iter = self.__make_addr_iter()
        data_iter = self.__make_data_iter(buffer)
        for trio in data_iter:
            self.__write_trio(trio, addr_iter.next())

    def __make_addr_iter(self):
        "Iterate over addresses of pixels to write to."
        addr_group = []
        for x in range(self.__width):
            for y in range(self.__height):
                addr_group.append((x, y))
                if len(addr_group) == 4:
                    yield tuple(addr_group)
                    addr_group = []

    @staticmethod
    def __make_data_iter(buffer):
        "Iterate over the buffer a block at a time."
        if len(buffer) % 3 != 0:
            raise ValueError('Buffer has a bad size!')
        data = ''
        for char in buffer:
            data += char
            if len(data) == 3:
                yield data
                data = ''

    def __write_trio(self, trio, addrs):
        "Write a 3-byte block to the pixels addresses given."
        duo_iter = self.__make_duo_iter(trio)
        tri_iter = self.__make_tri_iter(duo_iter)
        for (r_duo, g_duo, b_duo), addr in zip(tri_iter, addrs):
            r, g, b, a = self.__pixels[addr]
            r = self.__set_two_bits(r, r_duo)
            g = self.__set_two_bits(g, g_duo)
            b = self.__set_two_bits(b, b_duo)
            self.__pixels[addr] = r, g, b, a

    @staticmethod
    def __make_duo_iter(trio):
        "Iterate over 2-bits that need to be written."
        for char in trio:
            byte = ord(char)
            duos = []
            for _ in range(4):
                byte, duo = divmod(byte, 4)
                duos.append(duo)
            for duo in reversed(duos):
                yield duo

    @staticmethod
    def __make_tri_iter(duo_iter):
        "Group bits into their pixel units for writing."
        group = []
        for duo in duo_iter:
            group.append(duo)
            if len(group) == 3:
                yield tuple(group)
                group = []

    @staticmethod
    def __set_two_bits(byte, duo):
        "Write a duo (2-bit) group to a pixel channel (RGB)."
        if duo > 3:
            raise ValueError('Duo bits has to high of a value!')
        byte &= 252
        byte |= duo
        return byte

################################################################################

class ByteReader:

    "ByteReader(image) -> ByteReader instance"

    def __init__(self, image):
        "Initalize the ByteReader's internal variables."
        self.__width, self.__height = image.size
        self.__pixels = image.load()

    def read(self):
        "Read data out of a picture, decompress the data, and return it."
        compressed_data = ''
        addr_iter = self.__make_addr_iter()
        size_block = self.__read_blocks(addr_iter)
        size_value = self.__block_to_number(size_block)
        blocks_to_read = math.ceil(size_value / 3.0)
        for _ in range(blocks_to_read):
            compressed_data += self.__read_blocks(addr_iter)
        if len(compressed_data) != blocks_to_read * 3:
            raise ValueError('Blocks were not read correctly!')
        if len(compressed_data) > size_value:
            compressed_data = compressed_data[:size_value]
        return bz2.decompress(compressed_data)

    def __make_addr_iter(self):
        "Iterate over the pixel addresses in the image."
        addr_group = []
        for x in range(self.__width):
            for y in range(self.__height):
                addr_group.append((x, y))
                if len(addr_group) == 4:
                    yield tuple(addr_group)
                    addr_group = []

    def __read_blocks(self, addr_iter):
        "Read data a block at a time (4 pixels for 3 bytes)."
        pixels = []
        for addr in addr_iter.next():
            pixels.append(self.__pixels[addr])
        duos = self.__get_pixel_duos(pixels)
        data = ''
        buffer = []
        for duo in duos:
            buffer.append(duo)
            if len(buffer) == 4:
                value = 0
                for duo in buffer:
                    value <<= 2
                    value |= duo
                data += chr(value)
                buffer = []
        if len(data) != 3:
            raise ValueError('Data was not decoded properly!')
        return data

    @classmethod
    def __get_pixel_duos(cls, pixels):
        "Extract bits from a given group of pixels."
        duos = []
        for pixel in pixels:
            duos.extend(cls.__extract_duos(pixel))
        return duos

    @staticmethod
    def __extract_duos(pixel):
        "Retrieve the bits stored in a pixel."
        r, g, b, a = pixel
        return r & 3, g & 3, b & 3

    @staticmethod
    def __block_to_number(block):
        "Convert a block into a number (size of data buffer)."
        value = 0
        for char in block:
            value <<= 8
            value |= ord(char)
        return value

################################################################################

def main(picture, mode, text):
    "Dispatch the various operations that can be requested."
    image = Image.open(picture)
    if image.mode != 'RGBA':
        image = image.convert('RGBA')
    if mode == 'Evaluate':
        evaluate(image)
    elif mode == 'Simulate':
        simulate(image, text)
    elif mode == 'Encode':
        encode(image, text)
    elif mode == 'Decode':
        decode(image)
    else:
        raise ValueError('Mode %r was not recognized!' % mode)

################################################################################

def evaluate(image):
    "Display the number of bytes available in the image."
    print 'Usable bytes available =', bytes_in_image(image)

def bytes_in_image(image):
    "Calculate the number of usable bytes in an image."
    blocks = blocks_in_image(image)
    usable_blocks = blocks - 1
    usable_bytes = usable_blocks * BYTES_PER_BLOCK
    return min(usable_bytes, MAX_DATA_BYTES)

def blocks_in_image(image):
    "Find out how many blocks are in an image."
    width, height = image.size
    pixels = width * height
    blocks = pixels / PIXELS_PER_BLOCK
    return blocks

################################################################################

def simulate(image, text):
    "Find out how much space the text takes in the image that was given."
    compressed_data = bz2.compress(text)
    compressed_size = len(compressed_data)
    usable_bytes = bytes_in_image(image)
    space_leftover = usable_bytes - compressed_size
    if space_leftover > 0:
        print 'You still have %s more bytes for storage.' % space_leftover
    elif space_leftover < 0:
        print 'You overfilled the image by %s bytes.' % -space_leftover
    else:
        print 'This is a perfect fit!'

################################################################################

def encode(image, text):
    "Encodes text in image and returns picture to the browser."
    mutator = ByteWriter(image)
    mutator.write(text)
    output = cStringIO.StringIO()
    image.save(output, 'PNG')
    output.seek(0)
    print 'Content-Type: image/PNG'
    print output.read()

################################################################################

def decode(image):
    "Extract the original message and deliver it to the client."
    accessor = ByteReader(image)
    buffer = accessor.read()
    print buffer

################################################################################

if __name__ == '__builtin__':
    try:
        main(cStringIO.StringIO(PICTURE), MODE, TEXT)
    except SystemExit:
        pass

<强> Color_Disruptor

from cStringIO import StringIO
from PIL import Image
from random import randrange

def main(data, r_bits, g_bits, b_bits, a_bits):
    image = Image.open(data)
    if image.mode != 'RGBA':
        image = image.convert('RGBA')
    width, height = image.size
    array = image.load()
    data.close()
    for x in range(width):
        for y in range(height):
            r, g, b, a = array[x, y]
            r ^= randrange(r_bits)
            g ^= randrange(g_bits)
            b ^= randrange(b_bits)
            a ^= randrange(a_bits)
            array[x, y] = r, g, b, a
    data = StringIO()
    image.save(data, 'PNG')
    print 'Content-Type: image/PNG'
    print data.getvalue()

if __name__ == '__builtin__':
    main(StringIO(DATA), *map(lambda bits: 1 << int(bits), (R, G, B, A)))
相关问题