简体   繁体   中英

Reflecting points in an RGB image over a reflection line (MATLAB)

I have an RGB image of size MxNx3. Let's imagine we have a reflection line somewhere in the bottom half of the image. How do I reflect all the points above the line onto the line? I know what I need to do but I can't get it right on MATLAB. Thanks for any help.

For example, in the image below the blue line is the reflection line.

在此输入图像描述

Code

%%// Read input image
img =imread(IMG_FILEPATH);

%%// Let user select the mask, top of which will basically act 
%%// as the reflection line
figure,imshow(img)
[mask,xi,yi] = roipoly(img);

%%// Remove the last element as that is same as the first one
xi(end)=[];
yi(end)=[];

%%// Find the two corner points each on the left and right sides of the mask
pt_matrix = [xi yi]
[val,ind] = sort(xi)
left_two_pts = pt_matrix(ind(1:2),:)
right_two_pts = pt_matrix(ind(end-1:end),:)
four_pts = round([left_two_pts;right_two_pts])

%%// Remove a 5x5 neighborhood around the four corners, so that biggest
%%// blob that is the line could be separated out
BW1 = edge(mask,'canny');
for k = 1:4
    BW1(four_pts(k,2)-2:four_pts(k,2)+2,four_pts(k,1)-2:four_pts(k,2)+1) = 0;
end

%%// Get the biggest blob that is the reflection line
[L, num] = bwlabel(BW1);
counts = sum(bsxfun(@eq,L(:),1:num));
[~,ind] = max(counts);
BW1 = (L==ind);

%%// Connect the endpoints of the line to left and right sides of the image
xlimit = [find(sum(BW1,1),1) find(sum(BW1,1),1,'last')];
[row1,col1] = ind2sub(size(BW1),find(BW1));
BW1(row1(1),1:col1(1)-1)=1;
BW1(row1(end),col1(end)+1:end)=1;

%%// Select only one per column for the reflection
[xt0,yt0] = find(BW1);
[yt1,a2,a3] =unique(yt0,'first');
xt1=xt0(a2);
sz1 = size(BW1,1)-xt1;

%%// Perform the reflection
for k = 1:numel(yt1)
    img(xt1(k):end,k,:) = img(xt1(k):-1:xt1(k)-sz1(k),k,:);
end

figure,imshow(img)

Typical mask with roipoly would look like -

在此输入图像描述

Output

在此输入图像描述

Note: User has to select exactly two points on the left side to represent the left side border of the mask and exactly two points on the right side for the right side border. Also, there must enough image pixels on top of the line to reflect over the line.

I'm assuming the line is stored in a mask, as stated by the OP. I will assume the mask is black above the line and white below it. Here is a "fancy" way to solve the problem. :)

% 1. Open your image (MxNx3 matrix).
img = im2double(imread('original.png'));

% 2. Open your 'line image' as a logical array (MxNx3 matrix)
line = imread('line.png') > 0;

% 3. Now, we will "align" the upper part of the image based on the line,
%    so that the line will be straight at the bottom of the image. We will
%    do that by sorting the 'line image', moving the ones of the mask
%    above. The code was adapted from:
%    http://www.mathworks.com/matlabcentral/newsreader/view_thread/28715
upper = zeros(size(line));
upper(~line) = -1;
upper = sort(upper, 'descend');
upper(upper == -1) = img(~line);

% 4. Let's concatenate both the image with it's mirror below.
imgConcat = [upper; upper(end:-1:1, :, :)];

% 5. Also, The line mask will be concatenated to it's negative, and we'll
%    invert the order of the rows.
lineConcat = [line; ~line];
lineConcat = lineConcat(end:-1:1,:,:);

% 6. Now we repeat the "alignment procedure" used on step 4 so that the
%    image will be positioned on the upper part. We will also remove the
%    lower part, now containing only zeros.
mirror = zeros(size(lineConcat));
mirror(lineConcat) = -1;
mirror = sort(mirror, 'ascend');
mirror(mirror == -1) = imgConcat(lineConcat);
mirror = mirror(1:end/2,:,:);

Here you see the result (step by step);

在此输入图像描述

To generate that image, I used this code:

% Display the results, step by step (final result is in the 'mirror' matrix).
subplot(2,3,1), imshow(img, []); title('Step 1. Original image')
subplot(2,3,2), imshow(double(line), []); title('Step 2. Line image');
subplot(2,3,3), imshow(upper, []); title('Step 3. Image "alignment"');
subplot(2,3,4), imshow(imgConcat, []); title('Step 4. Mirror concatenation');
subplot(2,3,5), imshow(double(lineConcat), []); title('Step 5. Mask concatenation');
subplot(2,3,6), imshow(mirror, []); title('Step 6. Result by a final alignment');

Assuming that the line is already defined, the simple way to go about this would be to scan each column and, starting at the location of the line, copy as many elements from above the line as will fit below the line, in reverse order.

So for each column c , let line[c] be the row number of the line in that column. Then

maxR = size(c);
c(line[c]:end) = c(line[c]-1:-1:line[c]-(maxR - line[c] + 1));

That part at the end, line[c]-(maxR - line[c] + 1) is what tells it how much to copy. It just takes the distance between the bottom of the column to the line and uses that as the number of pixels to copy.

Of course this has to be generalized to account for 3 channels, but I didn't want to clutter up the concept. Other than that, you just have to put that in a loop over all the columns, c .

This also assumes that there is enough data above the line to fill the area below the line. If not, you'll have to decide how to handle this case.

I'm sure there are ways to vectorize this and take out the loop, but I'll leave that to the experts :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM