'Pandas - image to DataFrame
I want to convert an RGB image into a DataFrame, so that I have the co-ordinates of each pixel and their RGB value.
x y red green blue
0 0 0 154 0 0
1 1 0 149 111 0
2 2 0 153 0 5
3 0 1 154 0 9
4 1 1 154 10 10
5 2 1 154 0 0
I can extract the RGB into a DataFrame quite easily
colourImg = Image.open("test.png")
colourPixels = colourImg.convert("RGB")
colourArray = np.array(colourPixels.getdata())
df = pd.DataFrame(colourArray, columns=["red","green","blue"])
But I don't know how to get the X & Y coordinates in there. I could write a loop, but on a large image that takes a long time.
Solution 1:[1]
Try using np.indices
unfortunately it ends up with a array where the coordinate is the first dimension, but you can do a bit of np.moveaxis
to fix that.
colourImg = Image.open("test.png")
colourPixels = colourImg.convert("RGB")
colourArray = np.array(colourPixels.getdata()).reshape(colourImg.size + (3,))
indicesArray = np.moveaxis(np.indices(colourImg.size), 0, 2)
allArray = np.dstack((indicesArray, colourArray)).reshape((-1, 5))
df = pd.DataFrame(allArray, columns=["y", "x", "red","green","blue"])
It's not the pretiest, but it seems to work (edit: fixed x,y being the wrong way around).
Solution 2:[2]
I've named the coordinates 'col' and 'row' to be explicit and avoid confusion if the x-coordinate is reffering to the column number or row number of your original pixel array:
A = colourArray
# Create the multiindex we'll need for the series
index = pd.MultiIndex.from_product(
(*map(range, A.shape[:2]), ('r', 'g', 'b')),
names=('row', 'col', None)
)
# Can be chained but separated for use in explanation
df = pd.Series(A.flatten(), index=index)
df = df.unstack()
df = df.reset_index().reindex(columns=['col', 'row', 'r', 'g', 'b'])
Explanation:
pd.Series(A.flatten(), index=index)
will create a multiindex series where each channel intensity is accessible via df[row_n, col_n][channel_r_g_or_b]
. The df
variable (currently a series) will now look something like this:
row col
0 0 r 116
g 22
b 220
1 r 75
g 134
b 43
...
255 246 r 79
g 9
b 218
247 r 225
g 172
b 172
unstack()
will pivot the third index (channel index), returning a dataframe with columns b
, g
, r
with each row indexed by a multiindex of (row_n, col_n)
. The df
now looks like this:
b g r
row col
0 0 220 22 116
1 43 134 75
2 187 97 33
... ... ... ... ...
255 226 156 242 128
227 221 63 212
228 75 110 193
We then call reset_index()
to get rid of the (row_n, col_n)
multiindex and just have a flat 0..?(n_pixels-1)
index. The df
is now:
row col b g r
0 0 0 220 22 116
1 0 1 43 134 75
2 0 2 187 97 33
... ... ... ... ... ...
65506 255 226 156 242 128
65507 255 227 221 63 212
65508 255 228 75 110 193
And then a simple reindex()
to rearrange the columns into col
, row
, r
, g
, b
order.
Timings:
Now as for how fast this runs, well... for a 3-channel image, here are the timings:
Size Time
250x250 58.2 ms
500x500 251 ms
1000x1000 1.03 s
2500x2500 8.14 s
Admittedly not great on images > 1 MP. unstack()
can take a while after the df gets very large.
I've tried @davidsheldon's solution and it ran a lot quicker, for a 2500x2500 image, it took 244 ms, and a 10000x10000 image took 9.04 s.
Solution 3:[3]
I created a little package using the davidsheldon's answer. Thank you! You can use
pip install pd2img
to install it. It is also possible to reverse the process (save the dataframe as an image). By the way: If the image has an alpha channel, the dataframe will have an additional column for that!
Here is everything you need to know:
from pd2img import Pd2Img
df = Pd2Img(r"C:\Users\Gamer\Documents\Downloads\WhatsApp
Image 2022-04-21 at 5.07.14 PM.jpeg") # creating an instance
df.to_file_rgb('f:\\testimagefile1.png') # save the
dataframe to an RGB image
df.to_file_rgba('f:\\testimagefile2.png') # save the dataframe to an RGBA image
np3 = df.to_numpy_rgb() # convert the image to an numpy array (RGB, not BGR!)
np4 = df.to_numpy_rgba() # convert the image to an numpy array (RGBA)
print(df.df) # printing the dataframe
y x red green blue
0 0 0 155 150 144
1 0 1 155 150 144
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | Hans |