Image snip and paste. Draw a polygone on an image, then click and drag to move that part of the image around. Bonus points for resize and rotate.

5 次查看(过去 30 天)
Hi,
I'm essentially trying to recreate the functionality of the select tool in MS Paint, but using a polygone rather than a rectangle. I want the user to load an image, draw a polygone over the image, then click and drag that polygone to cut that section out of the image and move it somewhere else.
bonus points, as later I want the user to be able to resize and rotate that polygone. Also place mutiple polygons moving different parts of the same image.
Below is the best code I have so far. It loads tire.jpg but feel free to swap that for any other image.
The user can select and draw the polygone, the commented out lines of code after defining the polygon_mask test that the image is cut from original image in the shape of the polygone correctly. Which it is. That works fine.
The function roi_moving seems to work well too. Dragging the cut section of image within the polygone, and a black section is left behind at the polygones orignal location.
roi_moved seems to be the problem. It doesn't paste the polygone down after the user has finished moving it. So I tried using the code from roi_moving at the end of roi_moved as shown at the bottom but that didn't work.
I also tried a whole seperate approach using addlistener functions.
Finally I've tried asking ChatGPT but to no avail.
I'm also getting the below warning message coming from MATLAB.
Error Message: (shows as code, it's actually orange error text from the command window.
> In images.roi.internal/ROI/dragROI
In images.roi.internal.ROI
Warning: Error occurred while executing the listener callback for event MovingROI defined for class images.roi.Polygon:
Unable to perform assignment because the left and right sides have a different number of elements.
Error in test3>roi_moving (line 52)
new_image(repmat(new_polygon_mask, [1, 1, size(I, 3)])) = I(repmat(temp_mask, [1, 1, size(I, 3)]));
Error in test3>@(src,event)roi_moving(src,event,h_image,I) (line 28)
lh_moving = addlistener(h, 'MovingROI', @(src, event)roi_moving(src, event, h_image, I));
Error in images.roi.internal.ROI/dragROI
Error in images.roi.internal.ROI
> In images.roi.internal/ROI/dragROI
In images.roi.internal.ROI
%The best code I have so far.
% this is really close. It moves the image but doesn't drop it off where it should be
I = imread('tire.jpg');
figure;
h_image = imshow(I);
hold on;
% Allow the user to create and move polygonal ROIs
h = drawpolygon;
if ~isvalid(h)
return
end
pos = h.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the polygon
polygon_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(I, 1), size(I, 2));
% % Create an image section from the polygon
% section = I(min(pos_int(:,2)):max(pos_int(:,2)), min(pos_int(:,1)):max(pos_int(:,1)), :);
% figure
% h_section = imshow(section); % show rectancle of outer edge of polygone
% set(h_section, 'AlphaData', polygon_mask(min(pos_int(:,2)):max(pos_int(:,2)), min(pos_int(:,1)):max(pos_int(:,1)))); %show polygone only
% Set up listeners for the MovingROI and ROIMoved events
lh_moving = addlistener(h, 'MovingROI', @(src, event)roi_moving(src, event, h_image, I));
lh_moved = addlistener(h, 'ROIMoved', @(src, event)roi_moved(src, event, h_image, I));
% Callback function for when the ROI is moving
% Callback function for when the ROI is moving
function roi_moving(src, ~, h_image, I)
% Get the current position of the polygon
pos = src.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the new polygon position
new_polygon_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(I, 1), size(I, 2));
% Get the old polygon_mask
old_polygon_mask = src.UserData;
% Create a new mask of the same size as I
temp_mask = false(size(I, 1), size(I, 2));
temp_mask(1:size(old_polygon_mask, 1), 1:size(old_polygon_mask, 2)) = old_polygon_mask;
% Create a new image with the section cut from the original image
new_image = I;
new_image(repmat(new_polygon_mask, [1, 1, size(I, 3)])) = I(repmat(temp_mask, [1, 1, size(I, 3)]));
new_image(repmat(temp_mask, [1, 1, size(I, 3)])) = 0;
% Update the displayed image
set(h_image, 'CData', new_image);
end
% Callback function for when the ROI has been moved
function roi_moved(src, ~, h_image, I)
% Get the current position of the polygon
pos = src.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the new polygon position
new_polygon_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(I, 1), size(I, 2));
% Store the new_polygon_mask in the ROI's UserData property
src.UserData = new_polygon_mask;
% Update the displayed image
set(h_image, 'CData', I);
end
Modified roi_moved:
% Callback function for when the ROI has been moved
function roi_moved(src, ~, h_image, I)
% Get the current position of the polygon
pos = src.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the new polygon position
new_polygon_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(I, 1), size(I, 2));
% Get the old polygon_mask
old_polygon_mask = src.UserData;
% Create a new mask of the same size as I
temp_mask = false(size(I, 1), size(I, 2));
temp_mask(1:size(old_polygon_mask, 1), 1:size(old_polygon_mask, 2)) = old_polygon_mask;
% Create a new image with the section cut from the original image
new_image = I;
new_image(repmat(new_polygon_mask, [1, 1, size(I, 3)])) = I(repmat(temp_mask, [1, 1, size(I, 3)]));
new_image(repmat(temp_mask, [1, 1, size(I, 3)])) = 0;
% Update the displayed image
set(h_image, 'CData', new_image);
end

回答(1 个)

Image Analyst
Image Analyst 2023-5-1
See attached copy and paste demos.
  6 个评论
Gregory Smith
Gregory Smith 2023-5-2
Hi,
I've updated the code and made some progress.
1) My understanding is that the listener should triggar roi_moved any time the polygone is moved on the image, this works if the user draws and then moves the polygone, but it doens't work if the user click on the polygone a second time and then wants to move it again.
Do you know how I can pass polygones in and out? - So a user can load in a polygone, move it, set it down. Draw and load in a 2nd polygone and move, resize that.
1) I solved the rounding issue sausing the polygons to be slights different sizes by using a loop. It's not ideal but seems to work. imtranslate Migh clean this up though, I'll take a look into that. Thank you.
2) I then have another loop to color the old polygone black. Do you know how I can form a unit8 polygone thats all black in a shape defined by CurrentPoly_Original_mask?
Thank you so much or your help.
clear all
clc
Tireimage = imread('tire.jpg');
figure;
h_image = imshow(Tireimage);
hold on;
axis on
% Allow the user to create and move polygonal ROIs
CurrentPoly_Original = drawpolygon;
if ~isvalid(CurrentPoly_Original)
return
end
pos = CurrentPoly_Original.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the polygon
CurrentPoly_Original_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(Tireimage, 1), size(Tireimage, 2));
% % Create an image section from the polygon
% section = I(min(pos_int(:,2)):max(pos_int(:,2)), min(pos_int(:,1)):max(pos_int(:,1)), :);
% figure
% h_section = imshow(section); % show rectancle of outer edge of polygone
% set(h_section, 'AlphaData', polygon_mask(min(pos_int(:,2)):max(pos_int(:,2)), min(pos_int(:,1)):max(pos_int(:,1)))); %show polygone only
% Set up listeners for the MovingROI and ROIMoved events
src = CurrentPoly_Original;
%lh_moving = addlistener(CurrentPoly_Original , 'MovingROI', @(src, event)roi_moving(src, event, h_image, Tireimage, CurrentPoly_Original_mask));
lh_moved = addlistener(CurrentPoly_Original , 'ROIMoved', @(src, event)roi_moved(src, event, h_image, Tireimage, CurrentPoly_Original_mask));
% Callback function for when the ROI has been moved
function [src] = roi_moved(src, ~, h_image, Tireimage, CurrentPoly_Original_mask)
% Get the new position of the polygon
pos = src.Position;
% Round the position values to integers for indexing
pos_int = round(pos);
% Generate a mask from the new polygon position
new_polygon_mask = poly2mask(pos_int(:,1), pos_int(:,2), size(Tireimage, 1), size(Tireimage, 2));
[row1, col1] = find(CurrentPoly_Original_mask);
[row2, col2] = find(new_polygon_mask);
% Calculate the number of elements to copy
num_elements = min(length(row1), length(row2));
I_temp = Tireimage; % copy the original image.
% replace the copy from location with black, might not want this.
%Tireimage(CurrentPoly_Original_mask) = uint8([0,0,0]);
for k = 1:num_elements
Tireimage(row1(k), col1(k), :) = uint8([0,0,0]);
end
% copy and paste 1 pixel at a time
for k = 1:num_elements
Tireimage(row2(k), col2(k), :) = I_temp(row1(k), col1(k), :);
end
disp('image pasted')
imshow(Tireimage)
axis on
hold on
src = drawpolygon('Position', [src.Position(:, 1), src.Position(:, 2)]);
end

请先登录,再进行评论。

产品


版本

R2022b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by