How can I transform 'line' in Binary Image to function like y=x ? How can i find coordinates of line?

15 views (last 30 days)
I will detect the lines in the image and input this map to the PID controller as if it is a signal/function. For this, I need the coordinates of the black lines in the image. Can you help me?
a = imread('map.jpeg');
level = 0.510;
a2 = im2bw(a, level);
imshow(a2)
This is the original image.

Accepted Answer

Image Analyst
Image Analyst on 25 Dec 2021
Edited: Image Analyst on 25 Dec 2021
You can't just binarize the image and use
[y, x] = find(mask);
because your background is non-uniform. To handle the nonuniformity of the background I use a bottom hat filter. This first does a morphological closing which basically is like a local max in that it smears the local white background in covering up the line and giving an all white image. Then it subtracts that from the original image. The background subtracts away and all you're left with is the line. Then I threshold it. However the line is a bit fuzzy and has some height to it -- the line is not just one row high in every column. So there is a bit of jitter. But I scan the image getting the topmost row in each column of the line. Then to eliminate jitter I apply a moving average filter to smooth the y coordinates.
Try this. At the bottom, x and y are your coordinates.
% Demo by Image Analyst
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
clear; % Erase all existing variables. Or clearvars if you want.
workspace; % Make sure the workspace panel is showing.
format long g;
format compact;
fontSize = 15;
markerSize = 40;
%--------------------------------------------------------------------------------------------------------
% READ IN IMAGE
fileName = 'line.jpeg';
grayImage = imread(fileName);
% Get the dimensions of the image.
% numberOfColorChannels should be = 1 for a gray scale image, and 3 for an RGB color image.
[rows, columns, numberOfColorChannels] = size(grayImage)
if numberOfColorChannels > 1
% It's not really gray scale like we expected - it's color.
% Extract the blue channel (so the magenta lines will be white).
grayImage = grayImage(:, :, 3);
end
%--------------------------------------------------------------------------------------------------------
% Display the image.
subplot(2, 2, 1);
imshow(grayImage, []);
impixelinfo;
axis('on', 'image');
caption = sprintf('Original image is %d rows by %d columns', rows, columns);
title(caption, 'FontSize', fontSize);
hold on
drawnow;
% Maximize window.
g = gcf;
g.WindowState = 'maximized'
drawnow;
% Do a bottomhat filter. Background is not uniform and this filter will handle that nonuniformity.
filteredImage = imbothat(grayImage, true(5));
% Display the image.
subplot(2, 2, 2);
imshow(filteredImage, []);
impixelinfo;
axis('on', 'image');
caption = sprintf('Filtered image is %d rows by %d columns', rows, columns);
title(caption, 'FontSize', fontSize);
hold on
drawnow;
% Optional: Show histogram of bottom hat image.
counts = histcounts(filteredImage);
subplot(2, 2, 3);
bar(counts)
title('Histogram of Bottom Hat filtered Image')
xlabel('GrayLevel')
ylabel('Counts')
grid on;
lowThreshold = 6;
highThreshold = 255;
% [lowThreshold, highThreshold] = threshold(lowThreshold, highThreshold, filteredImage)
% Get a binary image
xline(lowThreshold, 'Color', 'r', 'LineWidth', 2)
mask = filteredImage > lowThreshold;
% For noise reduction, Fill any potential holes and take the largest blob.
mask = bwareafilt(imfill(mask, 'holes'), 1);
% Now get the skeleton (centerline)
% mask = bwmorph(mask, 'skel', inf); % Sorry - takes too long.
% Display the image.
subplot(2, 2, 4);
imshow(mask, []);
impixelinfo;
axis('on', 'image');
title('Mask', 'FontSize', fontSize);
drawnow;
% Scan the image getting the top row.
y = nan(1, columns);
for col = 1 : columns
t = find(mask(:, col), 1, 'first');
if ~isempty(t)
y(col) = t;
end
end
x = 1 : columns
% Get rid of any nans, where there was no line for those columns.
badCols = isnan(y);
y(badCols) = [];
x(badCols) = [];
% The line might be a little jagged so smooth it a bit.
y = movmean(y, 3);
% Plot over image in red
hold on;
plot(x, y, 'r-', 'LineWidth', 3);
  3 Comments
Image Analyst
Image Analyst on 26 Dec 2021
You'll need to calibrate. Take a picture of a scale or some object of known length then compute the number of mm per pixel. Now you can multiply all pixel measurements by the mm/pixel to get distances in mm. Then you need to somehow translate that number into a signal (voltage or digital number) to send your line following robot to tell it how far to move or turn.

Sign in to comment.

More Answers (1)

DGM
DGM on 25 Dec 2021
Edited: DGM on 25 Dec 2021
Raster Image Processing
You can do this by just processing the raster image as has been already described, but you'll have to deal with all the noise that comes with that process. Depending on your needs, that may be fine. Your case is probably the most forgiving example I've seen.
ROI Tools
I would suggest simply using ROI tools to approximate it as a piecewise linear function. The drawing is crude enough that accurate replication is probably superfluous. I doubt that the cusps on breakpoints and the waviness of the lines are actual features of the function being described. I'm assuming those are just artifacts.
A = imread('line.jpeg');
figure(1)
imshow(A);
h = drawpolyline(gca); % draw ROI, adjust points as necessary
%%
mask = createMask(h);
[y,x] = find(mask);
y = smooth(y,5);
figure(2)
plot(x,y,'b');
SVG Transcription
If you want a smooth and potentially accurate representation of the line, use something else to fit a spline to the curve and then process the spline itself instead of processing raster data.
% open image in inkscape, draw plot box, draw path, save as SVG
A = imread('line.jpeg');
fname = 'C.svg';
% spline discretization parameter [0 1]
coarseness = 0.01;
% get image geometry (needed to locate spline)
str = fileread(fname);
str = regexp(str,'((?<=<image)(.*?)(?=\/>))','match');
pbx = regexp(str,'((?<=x=")(.*?)(?="))','match');
pby = regexp(str,'((?<=y=")(.*?)(?="))','match');
pbw = regexp(str,'((?<=width=")(.*?)(?="))','match');
pbh = regexp(str,'((?<=height=")(.*?)(?="))','match');
pbrect = [str2double(pbx{1}{1}) str2double(pby{1}{1}) ...
str2double(pbw{1}{1}) str2double(pbh{1}{1})];
% get coordinates representing the curve
S = loadsvg(fname,coarseness,false); % see link above
x = S{1}(:,1); % assuming the first path is the correct one
y = S{1}(:,2);
% rescale to fit original image geometry
x = size(A,2)*(x-pbrect(1))/pbrect(3);
y = size(A,1)*(y-pbrect(2))/pbrect(4);
plot(x,y,'b')
xlim([0 1500])
ylim([100 800])
Note that I assumed that the straight lines are supposed to be straight and that the curved portion is supposed to be smooth. You're not forced to make an accurate replica of every potential defect in the drawing.
Other Notes
Regardless of the method used, you'll have to deal with two things that should be obvious now:
  • The y-axis is flipped
  • The x coordinates are nonuniformly spaced (might be important)
  • Your image and the derived coordinates have no calibration information
It's entirely up to you to define how the image coordinates correspond to the function described by the curve. If you already know where the breakpoints lie and/or what the relevant slopes are, then there's probably no point to any of this image analysis at all. The sensible thing to do would be to just do the math and make a piecewise function.
I'm assuming that this is just a profile for an oven or similar. If so, then the breakpoints, crest and slopes are figures of merit and should be something you already have. If all you have is a drawing, I have to question what substantiates its exactness of form.
For example, this would be the generation of a generic rate-duration defined profile.
% [startup preheat heat cooldown]
rate = [2 0.5 1.5 -3];
duration = [75 100 70 100];
x = cumsum([0 duration])
y = cumsum([0 rate.*duration])
xfine = linspace(x(1),x(end),100);
yfine = interp1(x,y,xf);
Though I made no attempt to tailor it to match yours, since I don't know that specific parts of it are critical. I don't even know if it's for a heater at all.
  6 Comments
Zeynep Calik
Zeynep Calik on 25 Dec 2021
Yes it is a pid controller for line following robot but without any sensor. As the first stage of my project, I gave a simple piecewise function as input to the system with using Simulink matlab function block.
function y = fsfc(x)
if (x>0 & x <= 2)
y = x;
elseif 2 < x & x <= 5
y = 2*x-2;
elseif 5 < x & x <= 10
y = -x+13;
elseif 10< x & x <= 30
y = sin(x)+ 3.544;
elseif 30 < x & x <= 35;
y = -x/2 +17.55596837591;
else
y=0;
end
end
Secondly, I need to determine the coordinates from a hand-drawn route as i shown in my question with the help of image processing and give them as input to the system.
Im so sorry for my bad englisg but I hope i can explain and thanks for sharing your knowledge
DGM
DGM on 26 Dec 2021
Edited: DGM on 26 Dec 2021
Ah well so I guess you kind of had to do it both ways then.
That should be enough information to calibrate the x,y data extracted from the image.

Sign in to comment.

Products


Release

R2021a

Community Treasure Hunt

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

Start Hunting!