In the input of a program is given height amount of lines that have width amount of RRGGBB values in them, with RR/GG/BB being a hexadecimal value of the corresponding color in an RGB format.
I need to take the input and convert it to an OpenCV image so that I could interact with it using the OpenCV library. How would I accomplish this?
Example of input:
https://drive.google.com/file/d/1XuKRuAiQLUv4rbVxl2xTgqYr_8JQeu63/view?usp=sharing
The first number is height, second is width, the rest of the text file is the image itself.
That is a really inefficient way to store an image, and this is a correspondingly inefficient way to unpack it!
#!/usr/bin/env python3
import numpy as np
import re
import cv2
# Read in entire file
with open('in.txt') as f:
s = f.read()
# Find anything that looks like numbers
l=re.findall(r'[0-9a-f]+',s)
# Determine height and width
height = int(l[0])
width = int(l[1])
# Create numpy array of BGR triplets
im = np.zeros((height,width,3), dtype=np.uint8)
i = 2
for row in range (height):
for col in range(width):
hex = l[i]
R = int(hex[0:2],16)
G = int(hex[2:4],16)
B = int(hex[4:6],16)
im[row,col] = (B,G,R)
i = i+1
# Save to disk
cv2.imwrite('result.png', im)
In case the data file disappears in future, this is how the first few lines look:
1080 1920
232215 18180b 18170b 18180b 18170a 181609 181708 171708 15160c 14170d
15170d 16170d 16160d 16170d 16170d 16170d 15160d 15160d 17170e 17180f
17180f 18180f 191a11 191a12 1c1c0f 1d1d0f 1e1d0f 1f1e10 1e1e10 1f1f12
202013 202113 212214 242413 242413 242413 242412 242410 242611 272610
272612 262712 262710 282811 27290f 2a2b10 2b2c12 2c2d12 2e3012 303210
Keywords: Python, Numpy, OpenCV, parse, hex, hexadecimal, image, image processing, regex
Related
I was wondering if this was possible.
I'm currently drafting a simple project that would transform my text files into images by using the values of the characters to determine the RGB values of the outputted image.
I know it sounds counterintuitive and no, I don't want to print a string into an image file, I want the text itself to determine the RGB values of each pixel. This is just a rough idea and is far from refined.
I just want a simple program that will work as a proof of concept.
Code so far:
#first contact
from ctypes import sizeof
from PIL import Image
import math as m
def test():
f='qran.txt'
file = open(f)
text = file.read()
file.close() # this is dumb, should just read from file instead of dumping it into a
text = list(text) #rudimentary fix, turn text into list so we can manage the characters
size = m.floor(m.sqrt(len(text)//3)) #round the value for a square image
print(size)
# for elem in text:
# print(ord(elem))
img = Image.new('RGB', (size,size))
pixels = img.load() # create the pixel map
c = 0
for i in range(img.size[0]): # for every col:
for j in range(img.size[1]): # For every row
pixels[i,j] = (ord(text[c]), ord(text[c+1]), ord(text[c+2])) # set the colour accordingly
c+=1
c+=1
img.show()
img.save('qran.png')
test()
As you can see right now my idea is working as a rough concept. You can copy the quran in plaintext and paste it in the same folder as this simple py program to see this output
The image comes out as dull, since characters are converted into integers and their values are too high, and so most colors come off as light-dark gray.
Are there some libraries that could help with exaggerating the values so that they would come off as more representative? I've thought of multiplying by 10 and truncating the result of inverting the values then applying some filters.
I know its pretty much trial and error by this point (as well as polishing the actual code to provide usable functions that allow tweaking images without editing the function over and over again) but I'd like some outside input from people that have dwelved into image processing and such in python.
I apologize in advance if this post was too wordy or contained some unnecessary tidbits, it's my first post in this community.
Just implementing Christoph's idea in the comments:
#!/usr/bin/env python3
from PIL import Image
import math as m
import pathlib
import numpy as np
# Load document as bytes
qran = pathlib.Path('qran.txt').read_bytes()
size = m.floor(m.sqrt(len(qran))) #round the value for a square image
# Make palette image from bytes
img = Image.frombuffer('P', (size,size), qran, "raw", 'P', 0, 1)
# Add random palette of 256 RGB triplets to image
palette = np.random.randint(0,256, 768, np.uint8)
img.putpalette(palette)
img.save('qran.png')
I am attempting to convert a sound file into an image, then back into that same sound file in Python. First, I'm reading the .wav with python's wave library, extract the frames, and then arrange the bytes as RGB tuples in a square image.
The output is cool and looks like this
but when I try to convert the image back to a soundfile, the result is horrid. Not sure what I'm doing wrong here
import wave
from PIL import Image
import numpy as np
from math import sqrt
w = wave.open("sample.wav", mode = "rb")
frames = w.readframes(w.getnframes())
pixels = []
#####FRAMES CONVERTED TO PIXEL TUPLES######
for i in range(0,w.getnframes(),3):
pixels.append((frames[i],frames[i+1],frames[i+2]))
#####FIT TO SQUARE IMAGE#####
dimensions = int(sqrt(w.getnframes()/3))
img = []
for x in range(0,dimensions):
row = []
for y in range(0,dimensions):
row.append(pixels[x*dimensions+y])
img.append(row)
array = np.array(img, dtype=np.uint8)
new_image = Image.fromarray(array)
new_image.save('new.png')
p = Image.open("new.png",mode="r")
flatten = [x for sets in list(p.getdata()) for x in sets]
###### WAV RE-CREATION ######
sampleRate = w.getframerate() # hertz
obj = wave.open('sound.wav','w')
obj.setnchannels(w.getnchannels())
obj.setsampwidth(2)
obj.setframerate(sampleRate)
for i in range(0,len(flatten)):
obj.writeframesraw(( flatten[i]).to_bytes(8,"big") )
obj.close()
You are introducing loss in your conversion to pixels.
First, you will lose one or two frames at the end with for i in range(0,w.getnframes(),3):, when the number of frames is not a multiple of three.
Second, your dimensions = int(sqrt(w.getnframes()/3)) and then writing dimensions squared pixels will lose many frames when the number of frames divided by three is not a square.
Third, and most importantly, you are ignoring the sample width, as well as the number of channels. You are only saving the low eight bits of each sample in the image. If the sample width is 16 bits, you are essentially saving noise in the image.
I am trying to process an image one pixel at a time and the pixel data is coming in 8 bit but ending up floating point. I'd like to preserve this data in a new image file. It doesn't matter what format tiff or exr...
the below example is just an example. So when I am writing the pixel pix[i,j] = (float,float,float)
I get an error expecting int.
How do I keep these values float and write a new file?
The other examples I see don't address this problem with writing one pixel at a time.
from PIL import Image
import colorsys
imgIn = Image.open("heightmapSmallLinear.png") # import source image
pix = imgIn.load()
size = imgIn.size
for i in range(size[0]): #loop over all pixels in x
for j in range(size[1]): # loop over all pixels in y
pixHSV = colorsys.rgb_to_hsv(pix[i,j][0]/255.0,pix[i,j][1]/255.0,pix[i,j][2]/255.0)#rgb pixel color to hsv #convert rgb to hsv
pix[i,j] = (pixHSV[0],pixHSV[1],pixHSV[2]) # write new pixel
imgIn.save('example.exr')#save image
I get an image stored as an object from a camera that look like this (here reduced to make it understandable):
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
is it possible to 'import' it as an 'image' in opencv?
I tried to look at the documentation of cv2.imdecode but could get it to work.
I could preprocess this array to get it to another format but I am not sure what could 'fit' to opencv.
Thank you for your help
This is a very succinct and pythonic (using NumPy) way to implement a conversion from your hexadecimal values matrix to an RGB matrix that could be read by OpenCV.
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
def to_rgb(v):
return np.array([np.int(v[1:3],16), np.int(v[3:5],16) , np.int(v[5:7],16)])
image_cv = np.array([to_rgb(h) for h in image.flatten()]).reshape(3, 4, 3)
cv2.imwrite('result.png', image_cv)
OpenCV requires either a RGB or a BGR input, which is to say you need to give the values of Red Green Blue or Blue Green Red on a scale from 0-255 (8 bit). I have shared with you the code to convert your array to an image.
Initially, I count the number of rows to find the height in terms of pixels. Then I count the number of items in a row to find the width.
Then I create an empty array of the given dimensions using np.zeros.
I then go to each cell and convert the hex code to its RGB equivalent, using the following formula #RRGGBB, R = int(RR,16), G = int(GG, 16), B = int(BB, 16). This converts the hexadecimal string to int.
#!/usr/bin/env python3
import numpy as np
import re
import cv2
# Your image
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
# Enter the image height and width
height = int(len(image[0]))
width = int(len(image[0][0]))
# Create numpy array of BGR triplets
im = np.zeros((height,width,3), dtype=np.uint8)
for row in range (height):
for col in range(width):
hex = image[row, col][1:]
R = int(hex[0:2],16)
G = int(hex[2:4],16)
B = int(hex[4:6],16)
im[row,col] = (B,G,R)
# Save to disk
cv2.imwrite('result.png', im)
I'm wanting to work on an idea using images but I can't get it to write pixel values properly, it always just ends up grey with some pattern like artefacts, and no matter what I try, the artefacts change but the image remains grey.
Here's the basic code I have:
from PIL import Image
data = ""
for i in range( 128**2 ):
data += "(255,0,0),"
im = Image.fromstring("RGB", (128,128), data)
im.save("test.png", "PNG")
There is no information in http://effbot.org/imagingbook/pil-index.htm on how to format data, so I've tried using 0-1, 0-255, 00000000-111111111 (binary), brackets, square bracks, no brackets, extra value for alpha and changing RGB to RGBA (which turns it light grey but that's it), comma after, and no comma after, but absolutely nothing has worked.
For the record, I'm not wanting to just store a single colour, I'm just doing this to initially get it working.
The format string should be arranged like:
"RGBRGBRGBRGB..."
Where R is a single character indicating the red value of a particular pixel, and the same for G and B.
"But 255 is three characters long, how can I turn that into a single character?" you ask. Use chr to convert your numbers into byte values.
from PIL import Image
data = ""
for i in range( 128**2 ):
data += chr(255) + chr(0) + chr(0)
im = Image.fromstring("RGB", (128,128), data)
im.save("test.png", "PNG")
Result:
Alternative solution:
Using fromstring is a rather roundabout way to generate an image if your data isn't already in that format. Instead, consider using Image.load to directly manipulate pixel values. Then you won't have to do any string conversion stuff.
from PIL import Image
im = Image.new("RGB", (128, 128))
pix = im.load()
for x in range(128):
for y in range(128):
pix[x,y] = (255,0,0)
im.save("test.png", "PNG")