Rotate a spot in a binary image by 45/-45 degree

17 views (last 30 days)
Hey Community!
I was wondering how I can rotate a specific rect / object in a binary image.
Let me explain!
At first I have a black matrix with an example size of 7x20. I put some ones into it like:
% img => 7x20 logical
midlerow = 4;
midlecol = 10;
witdh = 2;
whalf = 2/2;
hight = 6;
hhalf = 6/2;
img( (midlerow-whalf):(midlerow+whalf), (midlecol-hhalf):(midlecol+hhalf) ) = 1;
Lets say i the matrix looks like this afterwards:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
So the ones represent a white spot in a black image - looking like a recangular
Is there a way to rotate only this area consisting of ones, just by either 45/-45 or 135 / - 135 degree? Like a automatic way that is similar to my code?
As a furhter example:
I rotate by 90/-90 degree as follows:
img( (midlerow-hhalf):(midlerow+hhalf), (midlecol-whalf):(midlecol+whalf) ) = 1;
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
Thanks & best regards!
/edit: it should also work in simulink

Accepted Answer

Tommy
Tommy on 29 Apr 2020
Edited: Tommy on 29 Apr 2020
Quick note: I started working on this, then stepped away for a bit, then came back and finished. Despite ImageAnalyst's great answer which came sometime in the interim, I wanted to post anyway because I did have fun with it and I figured someone else might find it interesting!
--------------------------------------------------------------------------------------------
I had a bit of fun with this. Trying to rotate this image by 45 degrees:
img = false(70, 200);
midlerow = 36;
midlecol = 100;
witdh = 20;
whalf = 20/2;
hight = 60;
hhalf = 60/2;
img( (midlerow-whalf):(midlerow+whalf), (midlecol-hhalf):(midlecol+hhalf) ) = 1;
imshow(img);
(1) First I tried pulling out the coordinates of the white portion, shifting them so that their center is (0,0), multiplying by a rotation matrix, and then shifting the new coordinates back to where they came from:
[row, col] = ind2sub(size(img), find(img)); % get coordinates of white portion
pos = [row-midlerow, col-midlecol]; % move center to (0,0)
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)]; % rotation matrix
newPos = round(R(45)*pos') + [midlerow; midlecol]; % multiply and reposition new coordinates
newImg = false(70, 200);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true; % fill new image
end
figure; imshow(newImg);
Okay but not great. The (mathematical) image of our (actual) image under the above function does not include every point we'd like it to include. A given point [at, say, (2,3)] is mapped to a new point which almost definitely contains non-integer coordinates [for example (2,3) is mapped to (-0.7071,3.5355)], so we have to pick a valid nearby point. The above uses round to do this [so that (2,3) is ultimately mapped to (-1,4)], and this means that some points are never reached.
(2) I tried again using both ceil and floor on each input to generate twice the number of outputs [mapping (2,3) to both (0,4) and (-1,3), for example]:
[row, col] = ind2sub(size(img), find(img));
pos = [row-midlerow, col-midlecol];
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)];
newPos = [floor(R(45)*pos') ceil(R(45)*pos')] + [midlerow; midlecol];
newImg = false(70, 200);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true;
end
figure; imshow(newImg);
Better I guess, but not perfect. I'll bet using every possible combination (ceil on the x coordinate and floor on the y, ceil on the y and floor on the x, etc) might get you there... but that seems silly.
(3) A higher resolution of input points would work:
[row, col] = ind2sub(size(img), find(img));
[X,Y] = meshgrid(linspace(min(row)-midlerow, max(row)-midlerow, 1000),...
linspace(min(col)-midlecol, max(col)-midlecol, 1000));
betterPos = [X(:), Y(:)];
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)];
newPos = ceil(R(45)*betterPos') + [midlerow; midlecol];
newImg = false(70, 200);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true;
end
figure; imshow(newImg);
(4) imrotate deals with all the messy stuff, but it rotates entire images. You could cut the relevant portion of the image, rotate it, and then replace it:
diag = ceil(sqrt(witdh^2 + hight^2));
half = floor(diag/2);
rotImg = img( (midlerow-half):(midlerow+half), (midlecol-half):(midlecol+half) );
rotImg = imrotate(rotImg, 45, 'crop');
newImg = false(70, 200);
newImg( (midlerow-half):(midlerow+half), (midlecol-half):(midlecol+half) ) = rotImg;
figure; imshow(newImg);
That probably gives the best result yet, but I used 'crop' so that the rotated portion would stay the same size, and therefore I took care to make sure that the part which I did rotate was big enough so that none of the white portion was cropped away.
Conclusion: I think the fact that the test image is a simple rectangle made this a lot easier in each case. The general idea should still apply (mapping points to new points using a rotation matrix), but it would be harder to generate a fine resolution input (in the case of #3) or determine what portion of your image to cut out and pass to imrotate (in the case of #4).
EDIT
Expanding on #3 (greater resolution input) for a not-as-nice (but still pretty nice) image:
img = false(70, 200);
midlerow = 36;
midlecol = 100;
witdh = 20;
whalf = 20/2;
hight = 60;
hhalf = 60/2;
img( (midlerow-whalf):(midlerow+whalf), (midlecol-hhalf):(midlecol+hhalf) ) = 1;
img( (midlerow-whalf):(midlerow+whalf), (midlecol-hhalf/2):(midlecol+hhalf/2) ) = 0;
imshow(img);
[N, M] = size(img);
#2 (and #1) has the same problem as before:
[row, col] = ind2sub(size(img), find(img));
pos = [row-midlerow, col-midlecol];
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)];
newPos = [floor(R(45)*pos') ceil(R(45)*pos')] + [midlerow; midlecol];
newImg = false(N, M);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true;
end
figure; imshow(newImg);
#3 no longer works, because of how I set up the meshgrid using linspace from min to max (which basically assumes a rectangle):
[row, col] = ind2sub(size(img), find(img));
[X,Y] = meshgrid(linspace(min(row)-midlerow, max(row)-midlerow, 1000),...
linspace(min(col)-midlecol, max(col)-midlecol, 1000));
betterPos = [X(:), Y(:)];
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)];
newPos = ceil(R(45)*betterPos') + [midlerow; midlecol];
newImg = false(N, M);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true;
end
figure; imshow(newImg);
So instead, how about using imresize to achieve a higher resolution input:
factor = 10;
bigImg = imresize(img, factor);
[row, col] = ind2sub(size(bigImg), find(bigImg));
pos = [row-factor*midlerow, col-factor*midlecol];
R = @(angle) [cosd(angle) -sind(angle); sind(angle) cosd(angle)];
newPos = ceil(R(45)*pos') + factor*[midlerow; midlecol];
newImg = false(factor*N, factor*M);
for i = 1:size(newPos,2)
newImg(newPos(1,i), newPos(2,i)) = true;
end
newImg = imresize(newImg, 1/factor);
figure; imshow(newImg);
Nice. A factor of 10 and the default resizing method ('bicubic') was good enough for this example, but I doubt that that's always the case.
However, imrotate will probably almost always beat trying to transform the image yourself. I would seriously consider the ideas brought up in Image Analyst's answer.
  7 Comments
Domi
Domi on 3 May 2020
I respect the approaches you wrote here but i can not implement in Simulink with my rapsberry because of the computing time as well as the lack of support of the functions from matlab .. such as find etc.
I guess you can see the robot as a rect with 15px width and 20px height in thr image. His shape is not realy like this but it works out for the case of 0,180 as well as 90/-90 to cover him with "free space" of bin 0 in the binary image (map)
Domi
Domi on 3 May 2020
Image is still 78x120px. It's a live image from a camera which gets trough some image processing like edge detection & inflating and creates the binary image. Nonetheless the robot is seen as an white area (binary ones) which lets my algorithm think he is some kind of object..

Sign in to comment.

More Answers (1)

Image Analyst
Image Analyst on 29 Apr 2020
Simple? No. When you rotate, your canvass enlarges. So you need to first say if you want the canvass to enlarge or do you want to clip corners that pivot out of the original image boundaries. Then you need to say what your center of rotation is. imrotate() doesn't let you specify center of rotation so you'll have to do it yourself with the rotation matrix (look it up on Wikipedia). Then you'll have to paste it back onto the canvass at the right location. You'd have to figure out what the bounding box is after you make sure that your rotation point is not shifted. It's somewhat easier if you have a binary image (0 and 1) because you can use the 'Image' and 'Centroid' properties of regionprops to get the bounding box and centroid.
Here's a start:
props = regionprops(binaryImage, 'Image', 'Centroid');
for k = 1 : length(props)
thisImage = props(k).Image;
xCenter = props(k).Centroid(1);
yCenter = props(k).Centroid(2);
rotatedImage = imrotate(thisImage, 135, 'nearest', 'loose');
% Now paste it back on.
% However it gets tricky if the blob rotated out of the bounds of the original image!!!
end

Community Treasure Hunt

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

Start Hunting!