How can I quickly change pixels in a image from a

2020-01-30 01:00发布

I have an image, I want to change all the colors in the image from a color map eg. {(10,20,212) : (60,40,112)...}

Currently, I am reading the image OpenCV and then iterating over the image array and changing each pixel, but this is very slow.

Is there any way I can do it faster?

3条回答
神经病院院长
2楼-- · 2020-01-30 01:19

I am providing two answers to this question. This answer is more based in PIL/Pillow and the other is more based in OpenCV. Read this answer in conjunction with my other answer and potentially mix and match.

You can do it using the palette. In case you are unfamiliar with palettised images, rather than having an RGB value at each pixel location, you have a simple 8-bit index into a palette of up to 256 colours.

So, what we can do, is load your image as a PIL Image, and quantise it to the set of input colours you have. Then each pixel will have the index of the colour in your map. Then just replace the palette with the colours you want to map to.

#!/usr/bin/env python3

import numpy as np
from PIL import Image

def QuantizeToGivenPalette(im, palette):
    """Quantize image to a given palette.

    The input image is expected to be a PIL Image.
    The palette is expected to be a list of no more than 256 R,G,B values."""

    e = len(palette)
    assert e>0,    "Palette unexpectedly short"
    assert e<=768, "Palette unexpectedly long"
    assert e%3==0, "Palette not multiple of 3, so not RGB"

    # Make tiny, 1x1 new palette image
    p = Image.new("P", (1,1))

    # Zero-pad the palette to 256 RGB colours, i.e. 768 values and apply to image
    palette += (768-e)*[0]
    p.putpalette(palette)

    # Now quantize input image to the same palette as our little image
    return im.convert("RGB").quantize(palette=p)

# Open input image and palettise to "inPalette" so each pixel is replaced by palette index
# ... so all black pixels become 0, all red pixels become 1, all green pixels become 2...
im = Image.open('image.png').convert('RGB')

inPalette = [
    0,0,0,    # black
    255,0,0,  # red
    0,255,0,  # green
    0,0,255,  # blue
    255,255,255 # white
    ]
r = QuantizeToGivenPalette(im,inPalette)

# Now simply replace the palette leaving the indices unchanged
newPalette = [
    255,255,255,  # white
    0,255,255,    # cyan
    255,0,255,    # magenta
    255,255,0,    # yellow
    0,0,0         # black
    ]

# Zero-pad the palette to 256 RGB colours, i.e. 768 values
newPalette += (768-len(newPalette))*[0]

# And finally replace the palette with the new one
r.putpalette(newPalette)

# Save result
r.save('result.png')

Input Image

enter image description here

Output Image

enter image description here

So, to do specifically what you asked with a dictionary that maps old colour values to new ones, you will want to initialise oldPalette to the keys of your dictionary and newPalette to the values of your dictionary.

Keywords: Python, PIL, Pillow, image, image processing, quantise, quantize, specific palette, given palette, specified palette, known palette, remap, re-map, colormap, map.

There are some hopefully useful words about palettised images here, and here.

查看更多
该账号已被封号
3楼-- · 2020-01-30 01:36

I am providing two answers to this question. This answer is more based in OpenCV and the other is more based in PIL/Pillow. Read this answer in conjunction with my other answer and potentially mix and match.

You can use Numpy's linalg.norm() to find the distances between colours and then argmin() to choose the nearest. You can then use a LUT "Look Up Table" to look up a new value based on the existing values in an image.

#!/usr/bin/env python3

import numpy as np
import cv2

def QuantizeToGivenPalette(im, palette):
    """Quantize image to a given palette.

    The input image is expected to be a Numpy array.
    The palette is expected to be a list of R,G,B values."""

    # Calculate the distance to each palette entry from each pixel
    distance = np.linalg.norm(im[:,:,None] - palette[None,None,:], axis=3)

    # Now choose whichever one of the palette colours is nearest for each pixel
    palettised = np.argmin(distance, axis=2).astype(np.uint8)

    return palettised

# Open input image and palettise to "inPalette" so each pixel is replaced by palette index
# ... so all black pixels become 0, all red pixels become 1, all green pixels become 2...
im=cv2.imread("image.png",cv2.IMREAD_COLOR)

inPalette = np.array([
   [0,0,0],             # black
   [0,0,255],           # red
   [0,255,0],           # green
   [255,0,0],           # blue
   [255,255,255]],      # white
   dtype=np.uint8)

r = QuantizeToGivenPalette(im,inPalette)

# Now make LUT (Look Up Table) with the 5 new colours
LUT = np.zeros((5,3),dtype=np.uint8)
LUT[0]=[255,255,255]  # white
LUT[1]=[255,255,0]    # cyan
LUT[2]=[255,0,255]    # magenta
LUT[3]=[0,255,255]    # yellow
LUT[4]=[0,0,0]        # black

# Look up each pixel in the LUT
result = LUT[r]

# Save result
cv2.imwrite('result.png', result)

Input Image

enter image description here

Output Image

enter image description here

Keywords: Python, PIL, Pillow, image, image processing, quantise, quantize, specific palette, given palette, specified palette, known palette, remap, re-map, colormap, map, LUT, linalg.norm.

查看更多
兄弟一词,经得起流年.
4楼-- · 2020-01-30 01:40

I think you might find using the built in LUT function of opencv helpful, as documented here.

There is already a python binding for the function, and it takes as input the original matrix and a LUT, and returns the new matrix as an output.

There isn't a tutorial for using it in python, but there is one for using it in C++ which I imagine will be useful, found here. That tutorial lists this method as the fastest one for this sort of problem.

查看更多
登录 后发表回答