Editing image in Python via OpenCV and displaying it in PyQt5 ImageView? - python

I am taking a live image from a camera in Python and displaying it in an ImageView in my PyQt5 GUI, presenting it as a live feed.
Is is displaying fine, but I would like to draw a red crosshair on the center of the image to help the user know where the object of focus moved, relative to the center of the frame.
I tried drawing on it using "cv2.line(params)" but I do not see the lines. This is strange to me because in C++, when you draw on an image, it takes the mat and changes that mat in the code going forward. How can I display this on the UI window without having to make a separate call to cv2.imshow()?
This is the signal from the worker thread that changes the image, it emits an ndarray and a bool:
def pipeline_camera_acquire(self):
while True:
self.mutex.lock()
#get data and pass them from camera to img object
self.ximeaCam.get_image(self.img)
#get data from camera as numpy array
data_pic = self.img.get_image_data_numpy()
#Edits
cv2.line(data_pic, (-10,0), (10,0), (0,0,255), 1)
cv2.line(data_pic, (0,10), (0,-10), (0,0,255), 1)
self.editedIMG = np.rot90(data_pic, 3)
self.mutex.unlock()
#send signal to update GUI
self.imageChanged.emit(self.editedIMG, False)

I don't think that it isn't drawing the line, I think it just is drawing it out of the visible area. (0,0) is the upper right hand corner of the image, so (0,10),(0,-10), would be a thin line right at the edge of the image.
If you are trying to draw in the center then you should calculate it from the center of the numpy array.
For example:
x, y = data_pic.shape[0]//2, data_pic.shape[1]//2
cv2.line(data_pic, (x-10,y), (x+10,y), (0,0,255), 1)
cv2.line(data_pic, (x, y-10), (x, y+10), (0,0,255), 1)
That should draw the

Related

How to place one image on top of another, and update?

I don't know where to start:
from PIL import Image
im1 = Image.open('C:/background.png') # size = 1065x460px
I need to load an image that would be the "background", and on this image I will place a series of circles when a condition is met.
And when another condition is met, delete or update only the previous circle (the background must always remain) and place the new circle
I just edited the post:
Now I have an image that will be the background (that I will never erase)
and on the other hand I have the circles, which will be generated by graph.DrawCircle and my question, is there a way to update and delete these circles, that is, when I place the 2nd circle the 1st is deleted.
layout = [sg.Graph(canvas_size=(1065, 460), graph_bottom_left=(0, 0),
graph_top_right=(1065, 460), key="-GRAPH-")]
graph = window.Element("-GRAPH-")
def circle_position(x,y,r):
graph.DrawCircle((x,y), r, line_color='red')
before drawing a circle I have to erase all circles (but not the background) :
elif (event =="Display Error Code") or (event == 'Submit'):
# erasing all the circles------before drawing the new circle
circle_position(324,257,16)
if (values[0] == "1002") : # 2nd circle
# erasing all the circles------before drawing the new circle
circle_position(342,303,16)
Thanks again

Windows: Draw rectangle on camera preview

Similar questions have been asked a lot for Android, but so far I haven´t been able to find resources related to Windows OS. So basically, as the topic suggests, I would like to draw a rectangle on my camera preview. Some work has been done, but there´s still some problem in my program. Due to some limits, I would like to avoid using opencv as much as possible. Following is my approach:
Open Window´s built-in camera app
Run Python code that draws rectangle on screen, pixel by pixel (see below)
Click on screen with mouse to move rectangle with its upper-left corner
As you can see in the code, I´m not actually drawing on the camera preview but rather drawing on my screen, where the camer preview runs on one layer lower.
Here´s the python code:
import win32gui, win32ui, win32api, win32con
from win32api import GetSystemMetrics
dc = win32gui.GetDC(0)
dcObj = win32ui.CreateDCFromHandle(dc)
hwnd = win32gui.WindowFromPoint((0,0))
monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
red = win32api.RGB(255, 0, 0) # Red
past_coordinates = monitor
rec_x = 200 # width of rectangle
rec_y = 100 # height of rectangle
m = (100, 100) # initialize start coordinate
def is_mouse_down():
key_code = win32con.VK_LBUTTON
state = win32api.GetAsyncKeyState(key_code)
return state != 0
while True:
if(is_mouse_down() == True):
m = win32gui.GetCursorPos()
for x in range(rec_x):
win32gui.SetPixel(dc, m[0]+x, m[1], red)
win32gui.SetPixel(dc, m[0]+x, m[1]+rec_y, red)
for y in range(rec_y):
win32gui.SetPixel(dc, m[0], m[1]+y, red)
win32gui.SetPixel(dc, m[0]+rec_x, m[1]+y, red)
As a result, I´m able to draw a red rectangle. However, because the screen is constantly being refreshed, the two horizontal lines of my rectangle (see gif below) are shown as running dots that go from left to right. I can´t find or think of a way to improve this, whilst keeping the possibility to move the rectangle around per ckick.
PS. Ignore white rectangle. It´s a built-in thing of the camera app when you click anywhere on the preview.
Here are the references I used to get to this step:
How to draw an empty rectangle on screen with Python
https://python-forum.io/Thread-How-to-trigger-a-function-by-clicking-the-left-mouse-click

OpenCV draw line doesn't work on transparent image

I'm making a drawing program where you can later save and export your changes as an image. The non-transparent image option works perfectly, but the transparent option does not. The code I have right now is based on this post.
Whenever I draw a line in the transparent option, nothing on the image shows up. It's completely transparent.
print("Transparent:", str(transparent))
if not transparent:
image = np.zeros((height, width, 3), np.uint8) # initialize image for saving preferences
image[:] = backgroundColorBGR # change background color
for drawing in drawings: # loop through each drawing
cv2.line(image, drawing[0], drawing[1], drawing[2], thickness = drawing[3]) # create line that user drew
cv2.imwrite("yourimage.png", image)
else:
image = np.zeros((height, width, 4), dtype = np.uint8) # 4 channels instead of 3, for transparent images
for drawing in drawings: # loop through each drawing
cv2.line(image, drawing[0], drawing[1], drawing[2], thickness = drawing[3])
cv2.imwrite("yourimage.png", image)
Thanks to Mark Setchell, I found a working solution to this. In the color parameter in cv2.line(), pass a tuple where the first three values are the BGR color. The fourth value is for transparency/alpha. So your code should look like cv2.line(color=(0, 0, 200, 255)) # etc if you want to draw on a transparent image.
The OpenCV drawing functions are very limited and only intended for simple markings in images.
They do not support the concept of transparency in images. For example, the line method understands two modes: three channels (colored line) or non-three channels (grayscale line). That's it.
The result is that in your case, the same value is written to all four channels. A black line should stay invisible, while a white or blue line should end up as a white line.
Reference: Source code of the method

Finding origin pixel of image in OpenCV

In python openCV I am trying to create a GUI where the user has to pick pixels at set y coordinates. I can get the openCV pixel location that I want to set the mouse to, but I have no way of tying that to the overall system pixel which is needed for the win32api.SetCursorPos(). I have tried moving the image window with cv2.moveWindow('label', x, y) and then offsetting the cursor by y+offset, but this is a very inexact solution. Is there any way to find the current system pixel where the image origin pixel resides?
I'm not aware of a way to do this directly with OpenCV (after all, it's meant as convenience for prototyping, rather than a full fledged GUI framework), but since we're on Windows, we can hack it using the WinAPI directly.
N.B. There's a slight complication -- the callback returns image coordinates, so if scaling is enabled, our precision will be limited, and we have to do some extra work to map the coordinates back to client window coordinates.
Let's begin by investigating the window hierarchy create by OpenCV for the image display window. We could investigate the source code, but there's a quicker way, using the Spy++ tool from MSVS.
We can write a simple script to show some random data for this:
import cv2
import numpy as np
WINDOW_NAME = u'image'
img = np.zeros((512, 512), np.uint8)
cv2.randu(img, 0, 256)
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
cv2.imshow(WINDOW_NAME, img)
cv2.waitKey()
When we find this window in Spy++, we can see the following info.
There is the top level window, with a caption equal to the window name we specified, of class Main HighGUI class. This window contains a single child window, with no caption, and of class HighGUI class.
The following algorithm comes to mind:
Use FindWindow to find the top level window by caption, and get it's window handle.
Use GetWindow to get the handle of its child window.
Use GetClientRect to get the width and height of the client area (which contains the rendered image).
Transform the x and y image-relative coordinates back to client area space. (We need to know the dimensions of the current image to do this, so we will pass the current image as the user parameter of the callback.)
Transform the coordinates to screen space using ClientToScreen
Sample Script:
import win32gui
from win32con import GW_CHILD
import cv2
import numpy as np
# ============================================================================
def on_mouse(event, x, y, flags, img):
if event != cv2.EVENT_LBUTTONDOWN:
return
window_handle = win32gui.FindWindow(None, WINDOW_NAME)
child_window_handle = win32gui.GetWindow(window_handle, GW_CHILD)
(_, _, client_w, client_h) = win32gui.GetClientRect(child_window_handle)
image_h, image_w = img.shape[:2]
real_x = int(round((float(x) / image_w) * client_w))
real_y = int(round((float(y) / image_h) * client_h))
print win32gui.ClientToScreen(child_window_handle, (real_x, real_y))
# ----------------------------------------------------------------------------
def show_with_callback(name, img):
cv2.namedWindow(name, cv2.WINDOW_NORMAL)
cv2.setMouseCallback(name, on_mouse, img)
cv2.imshow(name, img)
cv2.waitKey()
cv2.destroyWindow(name)
# ============================================================================
WINDOW_NAME = u'image'
# Make some test image
img = np.zeros((512, 512), np.uint8)
cv2.randu(img, 0, 256)
show_with_callback(WINDOW_NAME, img)

OpenCV Python: Window Size and Mouse Events Coordinates

I'm trying to use mouse events and store the coordinates over which the cursor browsed. The problem is that my images are very small, 96x96 pixels and OpenCV chooses a window size that has bigger width than my images. So my image only takes the left side of the window. But the coordinates that are recognized by OpenCV correspond to the window size, so if I move the cursor to the middle of the window, only then are coordinates on the image itself marked at the middle. E.g. in this image the cursor was placed on the middle of the window and not the image:
I tried using the WindowResize function, but for some reason it does not work with images of such a small size, I'm assuming that this is the smallest window size in OpenCV.
Does anybody have any idea of how to make the mouse coordinates actually correspond to the coordinates in the image itself and not the window, or how to make the window size correspond exactly to the size of the image with very small images (96x96)?
I think it can be done by Scaling up your image size.
Here is some python code.
scaleFactor = 10
rows, cols = img.shape[:2]
img = cv2.resize(img, (scaleFactor*cols, scaleFactor*rows), interpolation=cv2.INTER_LINEAR)
Then get mouse position and scale down. ( pseudo code...)
px, py = getMouseClickPosition()
px /= scaleFactor
py /= scaleFactor

Categories

Resources