Displaying OpenCV iplimage data structures with wxPython - python

Here is my current code (language is Python):
newFrameImage = cv.QueryFrame(webcam)
newFrameImageFile = cv.SaveImage("temp.jpg",newFrameImage)
wxImage = wx.Image("temp.jpg", wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, wxImage, (0,0), (wxImage.GetWidth(), wxImage.GetHeight()))
I'm trying to display an iplimage captured from my webcam in a wxPython window. The problem is I don't want to store the image on hard disk first. Is there any way to convert an iplimage into another image format in memory? Any other solution?
I found a few "solutions" to this problem in other languages, but I'm still having trouble with this issue.
Thanks.

What you have to do is:
frame = cv.QueryFrame(self.cam) # Get the frame from the camera
cv.CvtColor(frame, frame, cv.CV_BGR2RGB) # Color correction
# if you don't do this your image will be greenish
wxImage = wx.EmptyImage(frame.width, frame.height) # If your camera doesn't give
# you the stream size, you might have to use (640, 480)
wxImage.SetData(frame.tostring()) # convert from cv.iplimage to wxImage
wx.StaticBitmap(self, -1, wxImage, (0,0),
(wxImage.GetWidth(), wxImage.GetHeight()))
I figured oyt out how to do this by looking at the Python OpenCV cookbook and at the wxPython wiki.

Yes, this question is old but I came here like everybody else searching for the answer. Several versions of wx, numpy, and opencv after the above solutions I figured I'd share a fast solution using cv2 and numpy images.
This is how to convert a NumPy array style image as used in OpenCV2 into a bitmap you can then set to a display element in wxPython (as of today):
import wx, cv2
import numpy as np
# Start with a numpy array style image I'll call "source"
# convert the colorspace to RGB from cv2 standard BGR, ensure input is uint8
img = cv2.cvtColor(np.uint8(source), cv2.cv.CV_BGR2RGB)
# get the height and width of the source image for buffer construction
h, w = img.shape[:2]
# make a wx style bitmap using the buffer converter
wxbmp = wx.BitmapFromBuffer(w, h, img)
# Example of how to use this to set a static bitmap element called "bitmap_1"
self.bitmap_1.SetBitmap(wxbmp)
Tested 10 minutes ago :)
This uses the built in wx function BitmapFromBuffer and takes advantage of the NumPy buffer interface so that all we have to do is swap the colors to get those in the expected order.

You could do with StringIO
stream = cStringIO.StringIO(data)
wxImage = wx.ImageFromStream(stream)
you can check more detail in \wx\lib\embeddedimage.py
just my 2 cents.

Related

How to slow down image display in Pillow

Probably an unusual question, but I am currently looking for a solution to display image files with PIL slower.
Ideally so that you can see how the image builds up, pixel by pixel from left to right.
Does anyone have an idea how to implement something like this?
It is a purely optical thing, so it is not essential.
Here an example:
from PIL import Image
im = Image.open("sample-image.png")
im.show()
Is there a way to "slow down" im.show()?
AFAIK, you cannot do this directly with PIL's Image.show() because it actually saves your image as a file to /var/tmp/XXX and then passes that file to your OS's standard image viewer to display on the screen and there is no further interaction with the viewer process after that. So, if you draw in another pixel, the viewer will not be aware and if you call Image.show() again, it will save a new copy of your image and invoke another viewer which will give you a second window rather than updating the first!
There are several possibilities to get around it:
use OpenCV's cv2.imshow() which does allow updates
use tkinter to display the changing image
create an animated GIF and start a new process to display that
I chose the first, using OpenCV, as the path of least resistance:
#!/usr/bin/env python3
import cv2
import numpy as np
from PIL import Image
# Open image
im = Image.open('paddington.png')
# Make BGR Numpy version for OpenCV
BGR = np.array(im)[:,:,::-1]
h, w = BGR.shape[:2]
# Make empty image to fill in slowly and display
d = np.zeros_like(BGR)
# Use "x" to avoid drawing and waiting for every single pixel
x=0
for y in range(h):
for x in range(w):
d[y,x] = BGR[y,x]
if x%400==0:
cv2.imshow("SlowLoader",d)
cv2.waitKey(1)
x += 1
# Wait for one final keypress to exit
cv2.waitKey(0)
Increase the 400 near the end to make it faster and update the screen after a greater number of pixels, or decrease it to make it update the screen after a smaller number of pixels meaning you will see them appear more slowly.
As I cannot share a movie on StackOverflow, I made an animated GIF to show how that looks:
I decided to try and do it with tkinter as well. I am no expert on tkinter but the following works just the same as the code above. If anyone knows tkinter better, please feel free to point out my inadequacies - I am happy to learn! Thank you.
#!/usr/bin/env python3
import numpy as np
from tkinter import *
from PIL import Image, ImageTk
# Create Tkinter Window and Label
root = Tk()
video = Label(root)
video.pack()
# Open image
im = Image.open('paddington.png')
# Make Numpy version for simpler pixel access
RGB = np.array(im)
h, w = RGB.shape[:2]
# Make empty image to fill in slowly and display
d = np.zeros_like(RGB)
# Use "x" to avoid drawing and waiting for every single pixel
x=0
for y in range(h):
for x in range(w):
d[y,x] = RGB[y,x]
if x%400==0:
# Convert the video for Tkinter
img = Image.fromarray(d)
imgtk = ImageTk.PhotoImage(image=img)
# Set the image on the label
video.config(image=imgtk)
# Update the window
root.update()
x += 1

Error when overlaying two images in OpenCV and or PIL

I've tried overlaying two images in openCV both in openCV and in PIL, but to no avail. I'm using a 1000x1000x3 array of np.zeros for the background (aka, a black background) and this random image of my monitor, but I really can't get it to work for some reason unbeknownst to me.
Trying with OpenCV only: (result(if you pay attention, you can see a couple of weird lines and dots in the middle))
base_temp = np.zeros((1000,1000,3))
foreground_temp = cv2.imread('exampleImageThatILinkedAbove.png')
base_temp[offset_y:offset_y+foreground_temp.shape[0], offset_x:offset_x+foreground_temp.shape[1]] = foreground_temp
Trying with PIL: (The result is literally the same as the OpenCV version)
base_temp = cv2.convertScaleAbs(self.base) #Convert to uint8 for cvtColor
base_temp = cv2.cvtColor(base_temp, cv2.COLOR_BGR2RGB) #PIL uses RGB and OpenCV uses BGR
base_temp = Image.fromarray(base_temp) #Convert to PIL Image
foreground_temp = cv2.cvtColor(cv2.convertScaleAbs(self.last_img), cv2.COLOR_BGR2RGB)
foreground_temp = Image.fromarray(foreground_temp)
base_temp.paste(foreground_temp, offset)
I'm using python3.5 and and OpenCV3.4 on Windows 10, if that's any help.
I'd like to avoid any solutions that require saving the cv2 images and then reloading them in another module to convert them but if it's unavoidable that's okay too. Any help would be appreciated!
If you check the type of base_temp, you will see it is float64 and that is going to cause you problems when you try to save it as a JPEG which expects unsigned 8-bit values.
So the solution is to create your base_temp image with the correct type:
base_temp = np.zeros((1000,1000,3), dtype=np.uint8)
The complete code and result look like this:
import cv2
import numpy as np
from PIL import Image
# Make black background - not square, so it shows up problems with swapped dimensions
base_temp=np.zeros((768,1024,3),dtype=np.uint8)
foreground_temp=cv2.imread('monitor.png')
# Paste with different x and y offsets so it is clear when indices are swapped
offset_y=80
offset_x=40
base_temp[offset_y:offset_y+foreground_temp.shape[0], offset_x:offset_x+foreground_temp.shape[1]] = foreground_temp
Image.fromarray(base_temp).save('result.png')

Using Matplotlib imshow to show GIF image

I need to show a background to a matplotlib plot using ax.imshow(). The background images that I will be using are GIF-images. Despite having PIL installed, the following code results in an error complaining that the Python Image Library (PIL) is not installed (which it is):
from pylab import imread
im_file = open("test.gif")
im_obj = imread(im_file)
Reading the image using PIL directly works better:
from PIL import Image
import numpy
img = Image.open("test.gif")
img_arr = asarray(img.getdata(), dtype=numpy.uint8)
However, when reshaping the array, the following code does not work:
img_arr = img_arr.reshape(img.size[0], img.size[1], 3) #Note the number 3
The reason is that the actual color information is contained in a color table accessed through img.getcolors() or img.getpalette().
Converting all the images to PNG or another suitable format that results in RGB images when opening them with imread() or Image.open() is not an option. I could convert the images when needed using PIL but I consider that solution ugly. So the question is as follows: Is there a simple and fast (the images are 5000 x 5000 pixels) way to convert the GIF images to RGB (in RAM) so that I can display them using imshow()?
You need to convert the GIF to RGB first:
img = Image.open("test.gif").convert('RGB')
See this question: Get pixel's RGB using PIL

convert openCV image into PIL Image in Python (for use with Zbar library)

I'm trying to use the Zbar library's QR code detection methods on images I extract with OpenCV's camera methods. Normally the QR code detection methods work with images (jpg, png, etc.) on my computer, but I guess the captured frames of OpenCV are different.
Is there a way of making the captured frame into a PIL Image?
Thank you.
from PIL import Image
import zbar
import cv2.cv as cv
capture = cv.CaptureFromCAM(1)
imgSize = cv.GetSize(cv.QueryFrame(capture))
img = cv.QueryFrame(capture)
#SOMETHING GOES HERE TO TURN FRAME INTO IMAGE
img = img.convert('L')
width, height = img.size
scanner = zbar.ImageScanner()
scanner.parse_config('enable')
zbar_img = zbar.Image(width, height, 'Y800', img.tostring())
# scan the image for barcodes
scanner.scan(zbar_img)
for symbol in zbar_img:
print symbol.data
With the python CV2, you can also do this:
import Image, cv2
cap = cv2.VideoCapture(0) # says we capture an image from a webcam
_,cv2_im = cap.read()
cv2_im = cv2.cvtColor(cv2_im,cv2.COLOR_BGR2RGB)
pil_im = Image.fromarray(cv2_im)
pil_im.show()
I think I may have found the answer. I'll edit later with results.
OpenCV to PIL Image
import Image, cv
cv_im = cv.CreateImage((320,200), cv.IPL_DEPTH_8U, 1)
pi = Image.fromstring("L", cv.GetSize(cv_im), cv_im.tostring())
Source: http://opencv.willowgarage.com/documentation/python/cookbook.html
Are you trying to obtain a RGB image? If that is the case, you need to change your parameters from this:
cv_im = cv.CreateImage((320,200), cv.IPL_DEPTH_8U, 1)
pi = Image.fromstring("L", cv.GetSize(cv_im), cv_im.tostring())
to that:
cv_im = cv.CreateImage((320,200), cv.IPL_DEPTH_8U, 3)
pi = Image.fromstring("RGB", cv.GetSize(cv_im), cv_im.tostring())
since it is documented almost nowhere, but the 'L' parameter of Image.fromstring is for 8-bit B&W images. Besides, you need to change the argument of your cv.CreateImage function from 1 (single channel image) to 3 (3 channels=RGB).
Hope it works for you.
Cheers
A simple way is to directly swap the channels. Suppose you are trying to convert a 3-channel image file between OpenCV format and PIL format. You can just use:
img[...,[0,2]]=img[...,[2,0]]
In this way, you won't be bothered with cv2.cvtColor as this function only works on images with certain depth.

Using PIL to fill empty image space with nearby colors (aka inpainting)

I create an image with PIL:
I need to fill in the empty space (depicted as black). I could easily fill it with a static color, but what I'd like to do is fill the pixels in with nearby colors. For example, the first pixel after the border might be a Gaussian blur of the filled-in pixels. Or perhaps a push-pull type algorithm described in The Lumigraph, Gortler, et al..
I need something that is not too slow because I have to run this on many images. I have access to other libraries, like numpy, and you can assume that I know the borders or a mask of the outside region or inside region. Any suggestions on how to approach this?
UPDATE:
As suggested by belisarius, opencv's inpaint method is perfect for this. Here's some python code that uses opencv to achieve what I wanted:
import Image, ImageDraw, cv
im = Image.open("u7XVL.png")
pix = im.load()
#create a mask of the background colors
# this is slow, but easy for example purposes
mask = Image.new('L', im.size)
maskdraw = ImageDraw.Draw(mask)
for x in range(im.size[0]):
for y in range(im.size[1]):
if pix[(x,y)] == (0,0,0):
maskdraw.point((x,y), 255)
#convert image and mask to opencv format
cv_im = cv.CreateImageHeader(im.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(cv_im, im.tostring())
cv_mask = cv.CreateImageHeader(mask.size, cv.IPL_DEPTH_8U, 1)
cv.SetData(cv_mask, mask.tostring())
#do the inpainting
cv_painted_im = cv.CloneImage(cv_im)
cv.Inpaint(cv_im, cv_mask, cv_painted_im, 3, cv.CV_INPAINT_NS)
#convert back to PIL
painted_im = Image.fromstring("RGB", cv.GetSize(cv_painted_im), cv_painted_im.tostring())
painted_im.show()
And the resulting image:
A method with nice results is the Navier-Stokes Image Restoration. I know OpenCV has it, don't know about PIL.
Your example:
I did it with Mathematica.
Edit
As per your reuquest, the code is:
i = Import["http://i.stack.imgur.com/uEPqc.png"];
Inpaint[i, ColorNegate#Binarize#i, Method -> "NavierStokes"]
The ColorNegate# ... part creates the replacement mask.
The filling is done with just the Inpaint[] command.
Depending on how you're deploying this application, another option might be to use the Gimp's python interface to do the image manipulation.
The doc page I linked to is oriented more towards writing GIMP plugins in python, rather than interacting with a background gimp instance from a python app, but I'm pretty sure that's also possible (it's been a while since I played with the gimp/python interface, I'm a little hazy).
You can also create the mask with the function CreateImage(), for instance:
inpaint_mask = cv.CreateImage(cv.GetSize(im), 8, 1)

Categories