简体   繁体   中英

Replace all pixels of multiple RGB values in OpenCV

I'm pretty new to image processing and python so bear with me

I'm trying to take a big image (5632x2048) which is basically a map of the world with provinces (ripped from Hearts of Iron 4), and each province is colored a different RGB value, and color it with a set of colors, each corresponding to a certain country. I'm currently using this code

import numpy as np
import cv2
import sqlite3

dbPath = 'PATH TO DB'
dirPath = 'PATH TO IMAGE'

con = sqlite3.connect(dbPath)
cur = con.cursor()

im = cv2.imread(dirPath)

cur.execute('SELECT * FROM Provinces ORDER BY id')
provinceTable = cur.fetchall()

for line in provinceTable:
    input_rgb = [line[1], line[2], line[3]]
    if line[7] == None:
        output_rgb = [255,255,255]
    else:
        output_rgb = line[7].replace('[', '').replace(']','').split(',')
    im[np.all(im == (int(input_rgb[0]), int(input_rgb[1]), int(input_rgb[2])), axis=-1)] = (int(output_rgb[0]), int(output_rgb[1]), int(output_rgb[2]))

cv2.imwrite('result.png',im)

The problem I'm running into is that it's painfully slow (50 minutes in and it hasn't finished), due to the fact I'm definitely using numpy wrong by looping through it instead of vectorizing (a concept I'm still new to and have no idea how to do). Google hasn't been very helpful either.

What's the best way to do this?

Edit: forgot to mention that the amount of values I'm replacing is pretty big (~15000)

As I mentioned in the comments, I think you'll want to use np.take(yourImage, LUT) where LUT is a Lookup Table.

So, if you make a dummy image the same shape as yours:

import numpy as np

# Make a dummy image of 5632x2048 RGB values
im = np.random.randint(0,256,(5632,2048,3), np.uint8)

that will be 34MB. Now reshape it to a tall vector of RGB values:

# Make image into a tall vector, as tall as necessary and 3 RGB values wide
v = im.reshape((-1,3))

which will be of shape (11534336, 3) and then flatten that to 24-bit values rather than three 8-bit values with np.dot()

# Make into tall vector of shape 11534336x1 rather than 11534336x3
v24 = np.dot(v.astype(np.uint32),[1,256,65536])

You will now have a 1-D vector of 24-bit pixel values with shape (11534336,)

Now create your RGB lookup table (I am making all 2^24 RGB entries here, you may need less):

RGBLUT = np.zeros((2**24,3),np.uint8)

And set up the LUT. So, supposing you want to map all colours in the original image to mid-grey (128) in the output image:

RGBLUT[:] = 128

Now do the np.dot() thing just the same as we did with the image so we get a LUT with shape (2 24,1) rather than shape (2 24,3):

LUT24 = np.dot(RGBLUT.astype(np.uint32), [1,256,65536])

Then do the actual lookup in the table:

result = np.take(LUT24, v24)

On my Mac, that take 334ms for your 5632x2048 image.


Then reshape and convert back to three 8-bit values by shifting and ANDing to undo effect of np.dot() .

I am not currently in a position to test the re-assembly, but it will look pretty much like this:

BlueChannel   = result & 0xff         # Blue channel is bottom 8 bits
GreenChannel  = (result>>8)  &0 xff   # Green channel is middle 8 bits
RedChannel    = (result>>16) &0 xff   # Red channel is top 8 bits

Now combine those three single channels into a 3-channel image:

RGB = np.dstack(RedChannel, GreenChannel, BlueChannel))

And reshape back from tall vector to dimensions of original image:

RGB = RGB.reshape(im.shape)

As regards setting up the LUT, to something more interesting than mid-grey, if you want to map say orange, ie rgb(255,128,0) to magenta, ie rgb(255,0,255) you would do something along the lines of:

LUT[np.dot([255,128,0],[1,256,65536])] = [255,0,255]  # map orange to magenta
LUT[np.dot([255,255,255],[1,256,65536])] = [0,0,0]  # map white to black
LUT[np.dot([0,0,0],[1,256,65536])] = [255,255,255]  # map black to white

Keywords : Python, image processing, LUT, RGB LUT 24-bit LUT, lookup table.

You can create a mask of the image first and use that to replace the colors. There's likely a pure numpy way of doing this that is faster, but I don't know it.

This code takes ~0.5 seconds to run. You should expect it to take about half a second for each color replacement.

import cv2
import numpy as np
import time

# make image
res = (5632, 2048, 3);
img = np.zeros(res, np.uint8);

# change black to white
black = (0,0,0);
white = (255,255,255);

# make a mask
start_time = time.time();
mask = cv2.inRange(img, black, black);
print("Mask Time: " + str(time.time() - start_time));

# replace color
start_time = time.time();
img[mask == 255] = white;
print("Replace Time: " + str(time.time() - start_time));

In terms of your code it'll look like this

for line in provinceTable:
    input_rgb = [line[1], line[2], line[3]]
    input_rgb = (int(input_rgb[0]), int(input_rgb[1]), int(input_rgb[2]))
    if line[7] == None:
        output_rgb = (255,255,255)
    else:
        output_rgb = line[7].replace('[', '').replace(']','').split(',')
        output_rgb = (int(output_rgb[0]), int(output_rgb[1]), int(output_rgb[2]))
    mask = cv2.inRange(im, input_rgb, input_rgb)
    im[mask == 255] = output_rgb

Here is one way to do that using Numpy and Python/OpenCV. Here I change red to green.

Input:

在此处输入图像描述

import cv2
import numpy as np

# load image
img = cv2.imread('test_red.png')

# change color
result = img.copy()
result[np.where((result==[0,0,255]).all(axis=2))] = [0,255,0]

# save output
cv2.imwrite('test_green.png', result)

# Display various images to see the steps
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Result:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM