I have a device which stores a grayscale image as a series of 8 bit unsigned integer values. I want to write a python program to read these images from a file and show them using wxBitmap. I have a code that works, but it seems inefficient due to a lot of conversions between formats.
Any suggestions for a faster code are highly appreciated.
My current code:
imagearray=numpy.fromfile(file=self.f, dtype=numpy.uint8, count=npixels).reshape(Height, Width)[::-1]
pilimage = Image.fromarray(imagearray)
rgb= pilimage.convert('RGB')
rgbdata = rgb.tostring()
WxBitmap = wx.EmptyBitmap(Width,Height)
WxBitmap.CopyFromBuffer(rgbdata)
output=WxBitmap
You can get a wxBitmap directly from a numpy array.
This is an example from wxPyWiki:
import wx, numpy
def GetBitmap( self, width=32, height=32, colour = (0,0,0) ):
array = numpy.zeros( (height, width, 3),'uint8')
array[:,:,] = colour
image = wx.EmptyImage(width,height)
image.SetData( array.tostring())
wxBitmap = image.ConvertToBitmap() # OR: wx.BitmapFromImage(image)
return wxBitmap
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')
Opening an image file to display in a frame or button in tkinter is simple, but I have images stored in arrays as raw RGB data (for transmission over SPI to a small LCD display). I wish to show this data as a proper image (exactly as processed for the small lcd display) in a frame or button, with tkinter.
I need some steering in how to approach this. Cheers.
You need to convert your array rgb data into an Image object that tkinter knows.
For this the best approach is to convert your rgb data to a PIL Image.
Depending on how your rgb values are ordered in the array you might need to do some conversions: if rgb data are interleaved (usually the case) or in separate channels/plans, the conversion might fail.
Check for here for converting an array to a pil image.
https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.fromarray
Once the conversion to Pil Image is done you can use it in tkinter.
from PIL import Image, ImageTk
import numpy as np
#convert array to PiL Image
PIL_image = Image.fromarray(np.uint8(your_rgb_array)).convert('RGB')
tk_image = ImageTk.PhotoImage(PIL_image )
label1 = tkinter.Label(image=tk_image )
For displaying the image you should have a look at tk.Canvas:
https://www.tutorialspoint.com/python/tk_canvas.htm
Thank you all for your input. I 've been chewing on it for a bit, and now have working code for the task. It takes the image as has been manipulated by numpy or opencv, transforms it to tk format and then shows the original and reduced bitdepth images side by side, with pixel doubling. Here is the snippet with the relevant code:
def nextimg(self):
f = self.image[self.ptr % self.length]
self.drawImage(f)
f = f[...,::-1]
f = np.repeat(f,2,axis=0)
f = np.repeat(f,2,axis=1)
temp=self.size
self.size=[480,480]
if self.bpp == 12: g = self.imageConvert_tk12(f)
elif self.bpp == 16: g = self.imageConvert_tk16(f)
else: g = self.imageConvert_tk18(f)
self.button_image1 = ImageTk.PhotoImage(Image.fromarray(np.uint8(f)).convert('RGB'))
self.button_image2 = ImageTk.PhotoImage(Image.fromarray(np.uint8(g)).convert('RGB'))
self.button_pic1.configure(image=self.button_image1)
self.button_pic2.configure(image=self.button_image2)
self.ptr+=1
self.size=temp
def imageConvert_tk18(self,image):
x,y = image.shape[0:2]
arr =np.ndarray((x,y,3))
arr[...,:] = (np.uint8(image[...,:] >> 2) * 4)
return arr
def imageConvert_tk16(self,image):
x,y = image.shape[0:2]
arr = np.ndarray((x,y,3))
arr[...,0:3:2] = (np.uint8(image[...,0:3:2] >> 3) * 8)
arr[...,1] = (np.uint8(image[...,1] >> 2) * 4)
return arr
def imageConvert_tk12(self,image):
x,y = image.shape[0:2]
arr =np.ndarray((x,y,3))
arr[...,:] = (np.uint8(image[...,:] >> 4) * 16)
return arr
I also understand a lot more now about lists and arrays. Opencv creates an np.ndarray , which I now understand is a 1-dimensional array (or list in list?) but with a given x-dimensional shape.
Of course I am hoping there might be another solution without having to resort to PIL as intermediary, but if not this will work fine.
Please feel free to suggest improvements to my code!
I am trying to convert a numpy array (cv2 image) to a wxpython Bitmap and display it properly. I have looked into various solutions on SO and elsewhere, but without success. You can see two of my attempts in the code below.
import wx
import cv2
import numpy as np
def create_wx_bitmap(cv2_image):
# type: (np.ndarray) -> wx.Bitmap
# My Attempt based on https://stackoverflow.com/questions/32995679/converting-wx-bitmap-to-numpy-using-bitmapbufferformat-rgba-python/32995940#32995940
height, width = cv2_image.shape[:2]
array = cv2_image # the OpenCV image
image = wx.Image(width, height)
image.SetData(array.tobytes())
wxBitmap = image.ConvertToBitmap()
return wxBitmap
# My other attempt:
# height, width = cv2_image.shape[:2]
# cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)
# return wx.Bitmap.FromBuffer(width, height, cv2_image_rgb)
class MyFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
cv2_image = cv2.imread("test1.png", cv2.IMREAD_ANYDEPTH | cv2.IMREAD_COLOR) # type: np.ndarray
print(cv2_image.dtype)
bitmap = create_wx_bitmap(cv2_image) # type: wx.Bitmap
wx.StaticBitmap(self, -1, bitmap, (0, 0), self.GetClientSize())
self.SetSize(bitmap.GetSize())
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame(None, "wxPython with OpenCV")
frame.Show()
app.MainLoop()
The code above seems to work for images under 16-bit (24 bit depth). However, an image that has a bit depth of 64 results in banding like the below screenshot. (which was a render from Blender 3D exported with 16 bit depth setting):
I have also tried converting the array datatype, but it didn't seem to make any difference.
Edit (The Final Solution):
The solution to my problem was to convert the array to np.uint8 after normalizing the data as mentioned in this SO answer. Thanks to #PetrBlahos for mentioning that the data needs to be 8bit RGB in his answer.
def create_wx_bitmap(cv2_image):
# type: (np.ndarray) -> wx.Bitmap
height, width = cv2_image.shape[:2]
info = np.iinfo(cv2_image.dtype) # Get the information of the incoming image type
data = cv2_image.astype(np.float64) / info.max # normalize the data to 0 - 1
data = 255 * data # Now scale by 255
cv2_image = data.astype(np.uint8)
cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)
return wx.Bitmap.FromBuffer(width, height, cv2_image_rgb)
I use
dc.DrawBitmap(wx.Bitmap.FromBuffer(iw, ih, cv_image), 0, 0)
but cv_image must be a numpy byte array of rgb values. So, whatever you do, you must convert your data to 8bit RGB (or possibly RGBA, the use FromBufferRGBA).
I do not quite see how your data is structured. 64bits means you have 4 channels (RGBA) each one is 16b integer?
I think you might use cv2.convertScaleAbs, or perhaps convertTo.
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
i am trying to use np's vectorize but imshow is showing a black image where it should be white if i understand vectorize correctly. i think the problem is the outputtype but i cant get it to work.
import numpy as np
import cv2
class Test():
def run(self):
arr = np.zeros((25,25))
arr[:]=255
cv2.imshow('white',arr)
flatarr = np.reshape(arr,25*25)
vfunc = np.vectorize(self.func)
#vfunc = np.vectorize(self.func,otypes=[np.int])#same effect
flatres = vfunc(flatarr)
shouldbewhite = np.reshape(flatres,(25,25))
cv2.imshow('shouldbewhite',shouldbewhite)
def func(self,a):
return 255
cv2.namedWindow('white',0)
cv2.namedWindow('shouldbewhite',0)
a = Test()
a.run()
cv2.waitKey(0)
From the docs :
The function imshow displays an image in the specified window. If the
window was created with the CV_WINDOW_AUTOSIZE flag, the image is
shown with its original size. Otherwise, the image is scaled to fit
the window. The function may scale the image, depending on its depth:
If the image is 8-bit unsigned, it is displayed as is.
If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to [0,255].
If the image is 32-bit floating-point, the pixel values are multiplied by 255. That
is, the value range [0,1] is mapped to [0,255].
If you run the following code:
class Test():
def run(self):
arr = np.zeros((25,25))
arr[:]=255
print arr.dtype
flatarr = np.reshape(arr,25*25)
vfunc = np.vectorize(self.func)
flatres = vfunc(flatarr)
print flatres.dtype
shouldbewhite = np.reshape(flatres,(25,25))
print shouldbewhite.dtype
def func(self,a):
return 255
You'll get something like:
float64
int32
int32
So your second case is divided by 256, and it being integer division, it rounds off to 0. Try with
vfunc = np.vectorize(self.func,otypes=[np.uint8])
and you may also want to consider replacing the first array with
arr = np.zeros((25,25), dtype='uint8')