Opencv How to Select a Region of Image Irregularly with Mouse Event? C/C++

opencv how can I select a region of image irregularly with mouse event? c/c++

I had a little attempt at this - it is probably not the cleanest code, but should give you some ideas.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

// Globals
bool finished=false;
Mat img,ROI;
vector<Point> vertices;

void
CallBackFunc(int event,int x,int y,int flags,void* userdata)
{
if(event==EVENT_RBUTTONDOWN){
cout << "Right mouse button clicked at (" << x << ", " << y << ")" << endl;
if(vertices.size()<2){
cout << "You need a minimum of three points!" << endl;
return;
}
// Close polygon
line(img,vertices[vertices.size()-1],vertices[0],Scalar(0,0,0));

// Mask is black with white where our ROI is
Mat mask= Mat::zeros(img.rows,img.cols,CV_8UC1);
vector<vector<Point>> pts{vertices};
fillPoly(mask,pts,Scalar(255,255,255));
img.copyTo(ROI,mask);
finished=true;

return;
}
if(event==EVENT_LBUTTONDOWN){
cout << "Left mouse button clicked at (" << x << ", " << y << ")" << endl;
if(vertices.size()==0){
// First click - just draw point
img.at<Vec3b>(x,y)=Vec3b(255,0,0);
} else {
// Second, or later click, draw line to previous vertex
line(img,Point(x,y),vertices[vertices.size()-1],Scalar(0,0,0));
}
vertices.push_back(Point(x,y));
return;
}
}

int main()
{
// Read image from file
img=imread("demo.jpg");

// Check it loaded
if(img.empty())
{
cout << "Error loading the image" << endl;
exit(1);
}

//Create a window
namedWindow("ImageDisplay",1);

// Register a mouse callback
setMouseCallback("ImageDisplay",CallBackFunc,nullptr);

// Main loop
while(!finished){
imshow("ImageDisplay",img);
waitKey(50);
}

// Show results
namedWindow("Result",1);
imshow("Result",ROI);
waitKey(5000);
}

Sample Image


Explanation of the Callback

At the beginning of main() I do this:

setMouseCallback("ImageDisplay",CallBackFunc,nullptr);

and that tells OpenCV to call the function CallBackFunc() for us whenever the mouse moves or is clicked.

CallBackFunc() is just a normal function like any other function. However, we don't ever call it ourselves - OpenCV calls it for us asynchronously (when we aren't expecting it). Because of that, we can't see any returned value from the function - since we never called it - and that is why it is declared as:

void CallBackFunc(...)

because it returns nothing, or a void, or a dirty great void of nothingness.

Ok, let's move on to the parameters it is called with. Basically, when the designers of OpenCV wrote the setMouseCallback() function, they couldn't know what I would want to pass to it as a parameter - maybe I would want to pass a filename, maybe I would want to pass a text string to draw on the image when the mouse is clicked, maybe I would want to pass a variable that I wanted updated with the mouse position. It could be anything. So, as they didn't know, they decided to say it is a "pointer to anything" and I can use it for whatever I like. Well, nothing is anything, so a pointer to void can point to anything. So, they say just pass a pointer to void that you know the meaning of. Then, inside CallBackFunc() you can just cast the pointer to whatever it was you passed since you know what it is.

So, in summary a pointer to void is just a place-marker for a pointer to something and you can decide what that something is without the OpenCV designers needing to know.

Hope that helps!

How can I display an image to me (as an user) and select a section of that image?

The comments I received from Mark Setchell and bfris enlightened me, one with a C++ code and another one with a recommendation of OpenCV.

But in addition to their comments, I found this article where they explain exactly what I want to do, so I consider my question answered. Select ROI or Multiple ROIs [Bounding box] in OPENCV python.

import cv2
import numpy as np

#image_path
img_path="image.jpeg"

#read image
img_raw = cv2.imread(img_path)

#select ROI function
roi = cv2.selectROI(img_raw)

#print rectangle points of selected roi
print(roi)

#Crop selected roi from raw image
roi_cropped = img_raw[int(roi[1]):int(roi[1]+roi[3]), int(roi[0]):int(roi[0]+roi[2])]

#show cropped image
cv2.imshow("ROI", roi_cropped)

cv2.imwrite("crop.jpeg",roi_cropped)

#hold window
cv2.waitKey(0)

or

import cv2
import numpy as np

#image_path
img_path="image.jpeg"

#read image
img_raw = cv2.imread(img_path)

#select ROIs function
ROIs = cv2.selectROIs("Select Rois",img_raw)

#print rectangle points of selected roi
print(ROIs)

#Crop selected roi ffrom raw image

#counter to save image with different name
crop_number=0

#loop over every bounding box save in array "ROIs"
for rect in ROIs:
x1=rect[0]
y1=rect[1]
x2=rect[2]
y2=rect[3]

#crop roi from original image
img_crop=img_raw[y1:y1+y2,x1:x1+x2]

#show cropped image
cv2.imshow("crop"+str(crop_number),img_crop)

#save cropped image
cv2.imwrite("crop"+str(crop_number)+".jpeg",img_crop)

crop_number+=1

#hold window
cv2.waitKey(0)

display a specific contour by clicking on location in the image

Transform your contour into polygon and use Opencv point polygon test.
https://docs.opencv.org/2.4/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.html

How to make a free hand shape(random) on an image in python using opencv

You asked how to draw any giver random shape on a picture using your computer's mouse. Here is a simple solution:

First, you will need to design a method that enables you to draw. So let's inspire ourselves from OpenCV: Mouse as a Paint-Brush where a method is used to draw common regular shapes such as a circle or a rectangle using a mouse. In your case, you will need random drawing as you could do with your hand.

So using that method you can draw points using the mouse and perform an interpolation between them using cv2.line() method:

cv2.line(im,(current_former_x,current_former_y),(former_x,former_y),(0,0,255),5)

Where im is the image you read and while you must memorize the former coordinates of the mouse position all the time:

current_former_x = former_x
current_former_y = former_y

Full OpenCV program:

Here is the code. Do not hesitate to comment anything you wouldn't understand:

'''
Created on Apr 3, 2016

@author: Bill BEGUERADJ
'''
import cv2
import numpy as np

drawing=False # true if mouse is pressed
mode=True # if True, draw rectangle. Press 'm' to toggle to curve

# mouse callback function
def begueradj_draw(event,former_x,former_y,flags,param):
global current_former_x,current_former_y,drawing, mode

if event==cv2.EVENT_LBUTTONDOWN:
drawing=True
current_former_x,current_former_y=former_x,former_y

elif event==cv2.EVENT_MOUSEMOVE:
if drawing==True:
if mode==True:
cv2.line(im,(current_former_x,current_former_y),(former_x,former_y),(0,0,255),5)
current_former_x = former_x
current_former_y = former_y
#print former_x,former_y
elif event==cv2.EVENT_LBUTTONUP:
drawing=False
if mode==True:
cv2.line(im,(current_former_x,current_former_y),(former_x,former_y),(0,0,255),5)
current_former_x = former_x
current_former_y = former_y
return former_x,former_y

im = cv2.imread("darwin.jpg")
cv2.namedWindow("Bill BEGUERADJ OpenCV")
cv2.setMouseCallback('Bill BEGUERADJ OpenCV',begueradj_draw)
while(1):
cv2.imshow('Bill BEGUERADJ OpenCV',im)
k=cv2.waitKey(1)&0xFF
if k==27:
break
cv2.destroyAllWindows()

Demo:

Sample Image

NumPy/OpenCV 2: how do I crop non-rectangular region?

*edit - updated to work with images that have an alpha channel.

This worked for me:

  • Make a mask with all black (all masked)
  • Fill a polygon with white in the shape of your ROI
  • combine the mask and your image to get the ROI with black everywhere else

You probably just want to keep the image and mask separate for functions that accept masks. However, I believe this does what you specifically asked for:

import cv2
import numpy as np

# original image
# -1 loads as-is so if it will be 3 or 4 channel as the original
image = cv2.imread('image.png', -1)
# mask defaulting to black for 3-channel and transparent for 4-channel
# (of course replace corners with yours)
mask = np.zeros(image.shape, dtype=np.uint8)
roi_corners = np.array([[(10,10), (300,300), (10,300)]], dtype=np.int32)
# fill the ROI so it doesn't get wiped out when the mask is applied
channel_count = image.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
# from Masterfool: use cv2.fillConvexPoly if you know it's convex

# apply the mask
masked_image = cv2.bitwise_and(image, mask)

# save the result
cv2.imwrite('image_masked.png', masked_image)

Selecting an area of an image with a mouse and recording the dimensions of the selection

Here's another, unfortunately much more involved way to do it (because it does several of the things you mentioned also wanting to do in comments to my first answer). It shades the area outside of the select, and does so using tkinter's vector-graphic (not PIL's image-processing) capabilities, which I think makes it the lighter-weight, and maybe faster, too, approach since it doesn't involve processing relatively-large amounts of image data and transferring it.

Originally I tried to draw the shaded outside area as a single continuous polygon, but that didn't work because tkinter doesn't support such concave polygonal shapes, so four border-less rectangles are drawn instead—plus an empty one with a just a border to outline the selected region (pictures below).

I borrowed a few interesting ideas used in a ActiveState Code » Recipe titled Pʏᴛʜᴏɴ Tᴋɪɴᴛᴇʀ Cᴀɴᴠᴀs Rᴇᴄᴛᴀɴɢʟᴇ Sᴇʟᴇᴄᴛɪᴏɴ Bᴏx by Sunjay Varma.

The code is object-oriented, which hopefully will make it easier to understand (and extend). Note you can get the current selection rectangle as two points by calling the MousePositionTracker class instance's cur_selection() method, so that could be used to get the information needed to do the actual image cropping (which likely will involve using PIL).

import tkinter as tk
from PIL import Image, ImageTk

class MousePositionTracker(tk.Frame):
""" Tkinter Canvas mouse position widget. """

def __init__(self, canvas):
self.canvas = canvas
self.canv_width = self.canvas.cget('width')
self.canv_height = self.canvas.cget('height')
self.reset()

# Create canvas cross-hair lines.
xhair_opts = dict(dash=(3, 2), fill='white', state=tk.HIDDEN)
self.lines = (self.canvas.create_line(0, 0, 0, self.canv_height, **xhair_opts),
self.canvas.create_line(0, 0, self.canv_width, 0, **xhair_opts))

def cur_selection(self):
return (self.start, self.end)

def begin(self, event):
self.hide()
self.start = (event.x, event.y) # Remember position (no drawing).

def update(self, event):
self.end = (event.x, event.y)
self._update(event)
self._command(self.start, (event.x, event.y)) # User callback.

def _update(self, event):
# Update cross-hair lines.
self.canvas.coords(self.lines[0], event.x, 0, event.x, self.canv_height)
self.canvas.coords(self.lines[1], 0, event.y, self.canv_width, event.y)
self.show()

def reset(self):
self.start = self.end = None

def hide(self):
self.canvas.itemconfigure(self.lines[0], state=tk.HIDDEN)
self.canvas.itemconfigure(self.lines[1], state=tk.HIDDEN)

def show(self):
self.canvas.itemconfigure(self.lines[0], state=tk.NORMAL)
self.canvas.itemconfigure(self.lines[1], state=tk.NORMAL)

def autodraw(self, command=lambda *args: None):
"""Setup automatic drawing; supports command option"""
self.reset()
self._command = command
self.canvas.bind("<Button-1>", self.begin)
self.canvas.bind("<B1-Motion>", self.update)
self.canvas.bind("<ButtonRelease-1>", self.quit)

def quit(self, event):
self.hide() # Hide cross-hairs.
self.reset()

class SelectionObject:
""" Widget to display a rectangular area on given canvas defined by two points
representing its diagonal.
"""
def __init__(self, canvas, select_opts):
# Create attributes needed to display selection.
self.canvas = canvas
self.select_opts1 = select_opts
self.width = self.canvas.cget('width')
self.height = self.canvas.cget('height')

# Options for areas outside rectanglar selection.
select_opts1 = self.select_opts1.copy() # Avoid modifying passed argument.
select_opts1.update(state=tk.HIDDEN) # Hide initially.
# Separate options for area inside rectanglar selection.
select_opts2 = dict(dash=(2, 2), fill='', outline='white', state=tk.HIDDEN)

# Initial extrema of inner and outer rectangles.
imin_x, imin_y, imax_x, imax_y = 0, 0, 1, 1
omin_x, omin_y, omax_x, omax_y = 0, 0, self.width, self.height

self.rects = (
# Area *outside* selection (inner) rectangle.
self.canvas.create_rectangle(omin_x, omin_y, omax_x, imin_y, **select_opts1),
self.canvas.create_rectangle(omin_x, imin_y, imin_x, imax_y, **select_opts1),
self.canvas.create_rectangle(imax_x, imin_y, omax_x, imax_y, **select_opts1),
self.canvas.create_rectangle(omin_x, imax_y, omax_x, omax_y, **select_opts1),
# Inner rectangle.
self.canvas.create_rectangle(imin_x, imin_y, imax_x, imax_y, **select_opts2)
)

def update(self, start, end):
# Current extrema of inner and outer rectangles.
imin_x, imin_y, imax_x, imax_y = self._get_coords(start, end)
omin_x, omin_y, omax_x, omax_y = 0, 0, self.width, self.height

# Update coords of all rectangles based on these extrema.
self.canvas.coords(self.rects[0], omin_x, omin_y, omax_x, imin_y),
self.canvas.coords(self.rects[1], omin_x, imin_y, imin_x, imax_y),
self.canvas.coords(self.rects[2], imax_x, imin_y, omax_x, imax_y),
self.canvas.coords(self.rects[3], omin_x, imax_y, omax_x, omax_y),
self.canvas.coords(self.rects[4], imin_x, imin_y, imax_x, imax_y),

for rect in self.rects: # Make sure all are now visible.
self.canvas.itemconfigure(rect, state=tk.NORMAL)

def _get_coords(self, start, end):
""" Determine coords of a polygon defined by the start and
end points one of the diagonals of a rectangular area.
"""
return (min((start[0], end[0])), min((start[1], end[1])),
max((start[0], end[0])), max((start[1], end[1])))

def hide(self):
for rect in self.rects:
self.canvas.itemconfigure(rect, state=tk.HIDDEN)

class Application(tk.Frame):

# Default selection object options.
SELECT_OPTS = dict(dash=(2, 2), stipple='gray25', fill='red',
outline='')

def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)

path = "Books.jpg"
img = ImageTk.PhotoImage(Image.open(path))
self.canvas = tk.Canvas(root, width=img.width(), height=img.height(),
borderwidth=0, highlightthickness=0)
self.canvas.pack(expand=True)

self.canvas.create_image(0, 0, image=img, anchor=tk.NW)
self.canvas.img = img # Keep reference.

# Create selection object to show current selection boundaries.
self.selection_obj = SelectionObject(self.canvas, self.SELECT_OPTS)

# Callback function to update it given two points of its diagonal.
def on_drag(start, end, **kwarg): # Must accept these arguments.
self.selection_obj.update(start, end)

# Create mouse position tracker that uses the function.
self.posn_tracker = MousePositionTracker(self.canvas)
self.posn_tracker.autodraw(command=on_drag) # Enable callbacks.

if __name__ == '__main__':

WIDTH, HEIGHT = 900, 900
BACKGROUND = 'grey'
TITLE = 'Image Cropper'

root = tk.Tk()
root.title(TITLE)
root.geometry('%sx%s' % (WIDTH, HEIGHT))
root.configure(background=BACKGROUND)

app = Application(root, background=BACKGROUND)
app.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.TRUE)
app.mainloop()

Here's some images showing it in action:

screenshot of start of selection process

screenshot of end of selection process



Related Topics



Leave a reply



Submit