Reload and zoom image - python

I am playing a little with a 100x100 pixel image, creating and updating it all the time. My code works fine, but I want the image to be shown and updated as the program runs, zoomed in so much that I can see the separate pixels.
Currently, I just open the resulting image in Eye of Gnome, which reloads automatically. Problem here, is that at every reload, the zoom level jumps back to 100% (and it should be at 600% or so).
while self.running:
img.save("image.tmp.png")
time.sleep(1)
os.rename("image.tmp.png", "image.png")
Trying to use PIL's show method works, but creates a new window per view.
while self.running:
img.show()
How can I reload the image all the time, while retaining the zoom level?

You can try to launch tkinter (install package python3-tk in Linux, don't know about other OSes) as separate process:
from PIL import ImageTk, Image
from scipy.ndimage import rotate
from scipy.misc import imresize
import numpy as np
import time
# do not import *, as there is another Image
from tkinter import NW, Tk, Canvas, Label
from multiprocessing import Process, Queue
# initial image
image = Image.open("img.png")
# convert to numpy
image = np.array(image)
# process which will show updated images
def display_process(q):
# initialize window and label which will show images
master = Tk()
label = Label(master)
label.pack()
# take numpy image from Queue, draw in on canvas
def change_img():
# get one image from queue.
# if there is no one, None will be received
image = q.get()
# if not image received yet, skip
if image is not None:
print(image.shape)
# image array should be uint8 type with 3 channels (x, x, 3)
photo = ImageTk.PhotoImage(image = Image.fromarray(image))
label.configure(image=photo)
label.image = photo
# needed to schedule next update:
master.update_idletasks()
# check for new image after 50ms
master.after(50, change_img)
change_img()
master.mainloop() # locks process
# Queue, through which exchange will be made
q = Queue()
q.put(image)
p = Process(target=display_process, args=(q,))
p.daemon = True # exit process on program exit
p.start() # start process
# main program (your loop code here) ----------
# Example:
for i in range(10):
# take numpy image, rotate it
image = rotate(image, 90)
# you can use fixed zoom factor for your images
# interp='nearest' uses nearest neghbor interpolation without smoothing
# (so you can see pixels)
image2 = imresize(image, 5.0, interp='nearest')
# send it to process
q.put(image2)
time.sleep(0.5)

Related

Is there any way with Tkinter to fade out widgets?

Is there any way to set an image as partially transparent, maybe using PIL or something? I know tkinter has a feature like this for Tk() and Toplevel(), but I wanted to know if there is a way to apply it to an a widget or maybe a PIL image that I can then put in a widget, either would do.
I want to make a game that fades to black when you lose, but I don't want the whole window to fade away.
You have two options: fade out the whole window or fade out an image using PIL, individual widgets cannot be faded out:
Fading out a window
Tk and TopLevel windows can be faded out entirely
import time
import threading
import tkinter
root = tkinter.Tk()
def fade():
global root
# Walk backwards through opacities (1 is opaque, 0 is transparent)
i = 1.0
while i >= 0:
root.attributes("-alpha", i)
i -= 0.1
# Sleep some time to make the transition not immediate
time.sleep(0.05)
# Put image fading in a thread so it doesn't block our GUI
fade_thread = threading.Thread(target=fade)
tkinter.Button(root, text="Fade out", command=fade_thread.start).pack()
root.mainloop()
Fading out an image
A bit more involved and a bit more computationally intensive (larger images exacerbate this problem). It could be worth precomputing these or using less steps (-10 vs -5 etc) to save some compute power.
import time
import threading
import tkinter
from PIL import Image, ImageTk
root = tkinter.Tk()
# Tested with .jpg and .png
IMAGE_PATH = "/path/to/image.jpg"
# Create a pillow image and a tkinter image. convert to RGBA to add alpha channel to image
image = Image.open(IMAGE_PATH).convert("RGBA")
image_tk = ImageTk.PhotoImage(image)
# We'll fade to whatever the background is here (black, white, orange, etc)
label = tkinter.Label(root, image=image_tk, bg="black")
label.pack()
def fade_image():
global image, image_tk, label
# Walk backwards through opacities (255 is opaque, 0 is transparent)
for i in range(255, 0, -5):
image.putalpha(i) # Set new alpha
image_tk = ImageTk.PhotoImage(image) # Cretae new image_tk
label.configure(image=image_tk)
# Sleep some time to make the transition not immediate
time.sleep(0.001)
# Put image fading in a thread so it doesn't block our GUI
fade_thread = threading.Thread(target=fade_image)
tkinter.Button(root, text="Fade To Black", command=fade_thread.start).pack()
root.mainloop()
Note this is bad practice in regards to using threads and time.sleep() inside a GUI program. Using widget.after(delay_in_ms, callback) is preferable. For more on how to do that check out tkinter: how to use after method

How to use python Tkinter to iterate images?

How to use python Tkinter to iterate images?
import tkinter as tk
from PIL import ImageTk, Image
win = tk.Tk()
win.geometry('800x500') # set window size
win.resizable(0, 0) # fix window
images = ['01.jpg', '02.jpg', '03.jpg']
def next_img():
# show next image
for img in images:
img = Image.open(img)
img = ImageTk.PhotoImage(img)
panel = tk.Label(win, image=img)
panel.pack()
btn = tk.Button(text='Next image', command=next_img)
btn.pack()
win.mainloop()
But my panel doesn't show any images. I hope the panel waits me and I click the button to show next images. How to solve it.
The reason why your code doesn't display any images is a bit complicated. There are two factors working together:
Because you're creating your labels (the ones that you use to display the images) in a loop, you end up creating 3 labels and 3 buttons - one for each image. All of these are arranged below each other, so depending on the size of your images, some of them might be below the window's bottom edge. If you use small images, you'll see three "Next image" buttons in your window.
Tkinter images are garbage collected if you don't keep a reference to them in your python code. Since your loop overwrites the value of the img variable in each iteration, all images except the last one are garbage collected and aren't displayed.
To fix your code, first start by removing that loop. You don't need 3 labels and 3 buttons, and you don't need to load all 3 images immediately when the program starts either.
The code that loads and displays the image should be moved into the next_img function. You can update the label's image with the command panel['image'] = img.
In order to cycle through the list of images, it's easiest to use an iterator. You can turn your list of images into an iterator by calling images = iter(images). Then you can use the next function to get the next image from the iterator when you need it.
import tkinter as tk
from PIL import ImageTk, Image
win = tk.Tk()
win.geometry('800x500') # set window size
win.resizable(0, 0) # fix window
panel = tk.Label(win)
panel.pack()
images = ['01.jpg', '02.jpg', '03.jpg']
images = iter(images) # make an iterator
def next_img():
try:
img = next(images) # get the next image from the iterator
except StopIteration:
return # if there are no more images, do nothing
# load the image and display it
img = Image.open(img)
img = ImageTk.PhotoImage(img)
panel.img = img # keep a reference so it's not garbage collected
panel['image'] = img
btn = tk.Button(text='Next image', command=next_img)
btn.pack()
# show the first image
next_img()
win.mainloop()
This code will loop through the images once, and when the last image is reached, pressing the "Next image" button will have no effect. If you want to wrap around to the first image, you can itertools.cycle to create an infinitely looping iterator instead:
images = itertools.cycle(images)
Unfortunately, Tkinter is a little confusing and when placing an image in a label or button you will want to add on the line beneath
panel.photo = img
I'm not exactly sure why this works but it seems there are 2 values that take the image to display it.

How to properly scale/rotate images in pyqtgraph?

I have implemented pyqtgraph inside QGraphicsView in PyQt5. When I display the image the following way, it is stretched out and expands in the same aspect ratio as the screen. How do I fix this?
image = pg.ImageItem(asarray(Image.open('pic.png')) )
self.graphicsView.addItem(image)
image.rotate(270)
EDIT: found out how to rotate image, so I updated question with the solution. Now I am just trying to scale it properly.
You probably want something like:
import pyqtgraph as pg
from PIL import Image
from numpy import asarray
app = pg.mkQApp()
# Set up a window with ViewBox inside
gv = pg.GraphicsView()
vb = pg.ViewBox()
gv.setCentralItem(vb)
gv.show()
# configure view for images
vb.setAspectLocked()
vb.invertY()
# display image
img_data = asarray(Image.open('/home/luke/tmp/graph.png'))
image = pg.ImageItem(img_data, axisOrder='row-major')
vb.addItem(image)
The important pieces here that set the image scaling/orientation are:
using ImageItem(axisOrder='row-major') because image files are stored in row-major order
vb.invertY() because image files have the +y axis pointing downward
and vb.setAspectLocked() to keep the pixels square
I used np.rot90() instead, it's much faster and cythonable
image = pg.ImageItem(np.rot90(np.asarray(Image.open('pic.png'))))

how to make python perform a command after open cv detected a face?

I am building a robot. Pretty much all my code has been copied from other people's projects on tutorials.
I'm using a Raspberry Pi camera to detect faces, and I have this electric Nerf gun, that I want to fire, ONLY after opencv detects a face. Right now, my code fires the Nerf gun no matter if a face is detected or not. Please tell me what I have done wrong? I think the problem is in the if len(faces) > 0 area. Here is all my code.
program 1, called cbcnn.py
import RPi.GPIO as gpio
import time
def init():
gpio.setmode(gpio.BOARD)
gpio.setup(22, gpio.OUT)
def fire(tf):
init()
gpio.output(22, True)
time.sleep(tf)
gpio.cleanup()
print 'fire'
fire(3)
program 2, called cbfd2.py
import io
import picamera
import cv2
import numpy
from cbcnn import fire
#Create a memory stream so photos doesn't need to be saved in a file
stream = io.BytesIO()
#Get the picture (low resolution, so it should be quite fast)
#Here you can also specify other parameters (e.g.:rotate the image)
with picamera.PiCamera() as camera:
camera.resolution = (320, 240)
camera.vflip = True
camera.capture(stream, format='jpeg')
#Convert the picture into a numpy array
buff = numpy.fromstring(stream.getvalue(), dtype=numpy.uint8)
#Now creates an OpenCV image
image = cv2.imdecode(buff, 1)
#Load a cascade file for detecting faces
face_cascade = cv2.CascadeClassifier('/home/pi/cbot/faces.xml /haarcascade_frontalface_default.xml')
#Convert to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Look for faces in the image using the loaded cascade file
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
print "Found "+str(len(faces))+" face(s)"
if len(faces) > 0:
("fire")
#Draw a rectangle around every found face
for (x,y,w,h) in faces:
cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,0),2)
#Save the result image
cv2.imwrite('result.jpg',image)
It fires because you have a fire(3) right after you defined the fire(tf) function. And the command below only creates a tuple with 1 string value: ("fire"). It doesn't call the fire function.
if len(faces) > 0:
("fire")
If you want to fire only when faces are detected, move that fire(3) under this IF and remove it from the top.
BTW you're importing another thing here: from cbcnn import fire with the same name as your function. This will overwrite your function name and if you put fire(3) below your import line it probably throws an error. Either change you fire function fire(tf) to fire_rocket(tf) and change your fire(3) to fire_rocket(3) under the IF.
Or add this on your import line (which you actually aren't even using!) and you can keep your fire function name as is:
from cbcnn import fire as Fire
Edit after question was changed:
Fix the IF I mentioned above and put fire(some number) in there.
The reason it fires is because when you import something from another program it runs the whole script. Because fire(3) is on there it will automatically call the function when you import it.
To avoid this you have to either:
remove other parts from your cbcnn.py: print and fire(3)
Or
put those parts in this IF statement to only run them when you actually run cbcnn.py yourself, and not by importing it:
if __name__=='__main__':
print(fire)
fire(3)
I already answer you in another post but, where you are putting:
cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,0),2)
There you can put whatever you want, if you go into the for loop, it means you have detected a face. As you can see on the code you posted, in that case their a drawing a rectangle, but you can do anything there, the x,y,w and h are giving you the coordinates and size of the detected face.
import io
import picamera
import cv2
import numpy
import RPi.GPIO as gpio
import time
#Create a memory stream so photos doesn't need to be saved in a file
stream = io.BytesIO()
#Get the picture (low resolution, so it should be quite fast)
#Here you can also specify other parameters (e.g.:rotate the image)
with picamera.PiCamera() as camera:
camera.resolution = (320, 240)
camera.vflip = True
camera.capture(stream, format='jpeg')
#Convert the picture into a numpy array
buff = numpy.fromstring(stream.getvalue(), dtype=numpy.uint8)
#Now creates an OpenCV image
image = cv2.imdecode(buff, 1)
#Load a cascade file for detecting faces
face_cascade = cv2.CascadeClassifier('/home/pi/cbot/faces.xml/haarcascade_frontalface_default.xml')
#Convert to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Look for faces in the image using the loaded cascade file
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
print "Found "+str(len(faces))+" face(s)"
#Draw a rectangle around every found face
for (x,y,w,h) in faces:
cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,0),2)
def init():
gpio.setmode(gpio.BOARD)
gpio.setup(22, gpio.OUT)
def fire(tf):
init()
gpio.output(22, True)
time.sleep(tf)
gpio.cleanup()
print 'fire'
fire(3)
#Save the result image
cv2.imwrite('result.jpg',image)

Why are webcam images taken with Python so dark?

I've showed in various ways how to take images with a webcam in Python (see How can I take camera images with Python?). You can see that the images taken with Python are considerably darker than images taken with JavaScript. What is wrong?
Image example
The image on the left was taken with http://martin-thoma.com/html5/webcam/, the one on the right with the following Python code. Both were taken with the same (controlled) lightning situation (it was dark outside and I only had some electrical lights on) and the same webcam.
Code example
import cv2
camera_port = 0
camera = cv2.VideoCapture(camera_port)
return_value, image = camera.read()
cv2.imwrite("opencv.png", image)
del(camera) # so that others can use the camera as soon as possible
Question
Why is the image taken with Python image considerably darker than the one taken with JavaScript and how do I fix it?
(Getting a similar image quality; simply making it brighter will probably not fix it.)
Note to the "how do I fix it": It does not need to be opencv. If you know a possibility to take webcam images with Python with another package (or without a package) that is also ok.
Faced the same problem. I tried this and it works.
import cv2
camera_port = 0
ramp_frames = 30
camera = cv2.VideoCapture(camera_port)
def get_image():
retval, im = camera.read()
return im
for i in xrange(ramp_frames):
temp = camera.read()
camera_capture = get_image()
filename = "image.jpg"
cv2.imwrite(filename,camera_capture)
del(camera)
I think it's about adjusting the camera to light. The former
former and later images
I think that you have to wait for the camera to be ready.
This code works for me:
from SimpleCV import Camera
import time
cam = Camera()
time.sleep(3)
img = cam.getImage()
img.save("simplecv.png")
I took the idea from this answer and this is the most convincing explanation I found:
The first few frames are dark on some devices because it's the first
frame after initializing the camera and it may be required to pull a
few frames so that the camera has time to adjust brightness
automatically.
reference
So IMHO in order to be sure about the quality of the image, regardless of the programming language, at the startup of a camera device is necessary to wait a few seconds and/or discard a few frames before taking an image.
Tidying up Keerthana's answer results in my code looking like this
import cv2
import time
def main():
capture = capture_write()
def capture_write(filename="image.jpeg", port=0, ramp_frames=30, x=1280, y=720):
camera = cv2.VideoCapture(port)
# Set Resolution
camera.set(3, x)
camera.set(4, y)
# Adjust camera lighting
for i in range(ramp_frames):
temp = camera.read()
retval, im = camera.read()
cv2.imwrite(filename,im)
del(camera)
return True
if __name__ == '__main__':
main()

Categories

Resources