Skip to content

This Project was done as part of a course ECE513 Digital Image Processing at Colorado State University

Notifications You must be signed in to change notification settings

gannaramu/Traffic-Sign-Detection

Repository files navigation

clear all
close all
[baseFileName, folder] = uigetfile('*.*', 'Specify an image file');
fullImageFileName = fullfile(folder, baseFileName);

if ~exist(fullImageFileName, 'file')
    message = sprintf('This file does not exist:\n%s', fullImageFileName);
    uiwait(msgbox(message));
    return;
end

%%
[rgbImage storedColorMap] = imread(fullImageFileName);
[rows columns numberOfColorBands] = size(rgbImage);
imshow(rgbImage)

Original Image

figure_0.png

redBand = rgbImage(:, :, 1);
greenBand = rgbImage(:, :, 2);
blueBand = rgbImage(:, :, 3);
fontSize = 16;

[redObjectsMask,maskedRGBImage] = createMask(rgbImage);
channel2Min = 0.5900

redObjectsMask= uint8(redObjectsMask);
imshow(redObjectsMask, []);
caption = sprintf('Mask of Only\nThe Red Objects');
title(caption, 'FontSize', fontSize);

Mask after Color Thresholding

figure_1.png


%% keeping area larger than 100 pix

smallestAcceptableArea = 100;

% Get rid of small objects.  Note: bwareaopen returns a logical.
redObjectsMask = uint8(bwareaopen(redObjectsMask, smallestAcceptableArea));
figure
% subplot(1, 3, 1);
imshow(redObjectsMask, []);
caption = sprintf('bwareaopen() removed objects\nsmaller than %d pixels', smallestAcceptableArea);
title(caption, 'FontSize', fontSize);

Mask after removing objects smaller than 100px in area

figure_2.png



%% Smooth the border using a morphological closing operation, imclose().
structuringElement = strel('disk', 4);
redObjectsMask = imclose(redObjectsMask, structuringElement);
% subplot(1, 3,2);
figure
imshow(redObjectsMask, []);
fontSize = 16;
title('Border smoothed', 'FontSize', fontSize);

Mask after Borders Smoothening

figure_3.png


%% Fill in any holes in the regions, since they are most likely red also.
redObjectsMask = uint8(imfill(redObjectsMask, 'holes'));
% redObjectsMask_1=imfill(redObjectsMask, 'holes');
% subplot(1, 3, 3);
figure
imshow(redObjectsMask, []);
title('Regions Filled', 'FontSize', fontSize);

Fill in any holes in the regions, since they are most likely red also.

figure_4.png

%%
% You can only multiply integers if they are of the same type.
% (redObjectsMask is a logical array.)
% We need to convert the type of redObjectsMask to the same data type as redBand.
redObjectsMask = cast(redObjectsMask, class(redBand));

maskedImageR = redObjectsMask .* redBand;
maskedImageG = redObjectsMask .* greenBand;
maskedImageB = redObjectsMask .* blueBand;
maskedRGBImage = cat(3, maskedImageR, maskedImageG, maskedImageB);
% Show the masked off, original image.
figure
imshow(maskedRGBImage);
fontSize = 13;
caption = sprintf('Masked Original Image\nShowing Only the Red Objects');
title(caption, 'FontSize', fontSize);

Show the masked off, original image.

figure_5.png



%%

bw = redObjectsMask; %imbinarize(I);
bw_l = logical(bw) ;
%%
% Obtain the boundaries of all the coins in the binary image.
[B,L,N] = bwboundaries(bw,'holes');
STATS = regionprops(L, 'Centroid', 'Area', 'Perimeter','Circularity', 'Image'); % we need 'BoundingBox' and 'Extent'
Centroid = cat(1, STATS.Centroid);
Perimeter = cat(1,STATS.Perimeter);
Area = cat(1,STATS.Area);

numSidesDistance = FindNumberOfVertices(STATS, L);

CircleMetric = (Perimeter.^2)./(4*pi*Area);  %circularity metric
SquareMetric = NaN(N,1);
TriangleMetric = NaN(N,1);

%%
% Plot the boundary for one of the coins over the original image.
hf = figure;
imshow(rgbImage)
hold on;
dividingValues = PlotTheoreticalCircularity;

for k=1:length(B)
    boundary = B{k};
    stats=  STATS(k);
    
    
    
    
    plot(boundary(:,2), boundary(:,1), 'r', 'LineWidth', 2)
%     [rx,ry,boxArea] = minboundrect( boundary(:,2), boundary(:,1));  %x and y are flipped in images
%     width = sqrt( sum( (rx(2)-rx(1)).^2 + (ry(2)-ry(1)).^2));
%     height = sqrt( sum( (rx(2)-rx(3)).^2+ (ry(2)-ry(3)).^2));
%     aspectRatio = width/height;
%     if aspectRatio > 1,
%         aspectRatio = height/width;  %make aspect ratio less than unity
%     end
%     SquareMetric(k) = aspectRatio;    %aspect ratio of box sides
%     TriangleMetric(k) = Area(k)/boxArea;  %filled area vs box area
%     Requires MinBoundSuite
%     %for each boundary, fit to bounding box, and calculate some parameters
    
    % Use |reducepoly| to reduce the number of points defining the coin boundary.
    p = [boundary(:,2) boundary(:,1)];
    tolerance = 0.08; % choose suitable tolerance
    p_reduced = reducepoly(p,tolerance);
    % p_reduced = unique(p_reduced,'row')
    plot(p_reduced(:,1),p_reduced(:,2),...
        'color',[0 0 1],'linestyle','-','linewidth',2,...
        'marker','o','markersize',5)
    
    thisBlob = stats.Image;
    allBwAreas = bwarea(thisBlob);
    allPerimeters = stats.Perimeter;
    bwCircularities = (4 * pi *  allBwAreas) ./ allPerimeters.^2;
    %==============================================================
    % Determine the number of sizes according to the circularity
    % Get the circularity of this specific blob.
    thisCircularity = bwCircularities %sortedCircularities(blobNumber);
    % See which theoretical dividing value it's less than.
    % This will determine the number of sides it has.
    numSidesCircularity = find(thisCircularity < dividingValues, 1, 'first');
    % Assign a string naming the shape according to the distance algorithm.
    if numSidesCircularity == 3
        % Blob has 3 sides.
        theShapeCirc = 'triangle';
    elseif numSidesCircularity == 4
        % Blob has 4 sides.
        theShapeCirc = 'square';
    elseif numSidesCircularity == 5
        % Blob has 5 sides.
        theShapeCirc = 'pentagon';
    elseif numSidesCircularity == 6
        % Blob has 6 sides.
        theShapeCirc = 'hexagon';
    else
        % Blob has 7 or more sides.
        theShapeCirc = 'nearly circular';
    end
    
    %==============================================================
    % Determine the number of sizes according to the centroid-to-perimeter algorithm
    % Classify the shape by the centroid-to-perimeter algorithm which seems to be more accurate than the circularity algorithm.
    numSidesDist = numSidesDistance(k);
    % Assign a string naming the shape according to the distance algorithm.
    if numSidesDist == 3
        % Blob has 3 sides.
        theShapeDistance = 'triangle';
    elseif numSidesDist == 4
        % Blob has 4 sides.
        theShapeDistance = 'square';
    elseif numSidesDist == 5
        % Blob has 5 sides.
        theShapeDistance = 'pentagon';
    elseif numSidesDist == 6
        % Blob has 6 sides.
        theShapeDistance = 'hexagon';
    elseif numSidesDist == 8
        % Blob has 6 sides.
        theShapeDistance = 'octagon';
        
    elseif numSidesDist>9
        % Blob has 7 or more sides.
        theShapeDistance = 'nearly circular';
    else
        theShapeDistance = 'abnormal Shape';
        
    end
    
    poly = polyshape(p_reduced);
    Peri = perimeter(poly);
    Area_l = polyarea(p_reduced(:,1),p_reduced(:,2));
    
    if numSidesDist>=3 || (CircleMetric(k) < 1.1)
        
        
        pos=[ min(p_reduced(:,1))-10,min(p_reduced(:,2))-10, abs(max(p_reduced(:,1))- min(p_reduced(:,1)))+20,abs(max(p_reduced(:,2))-min(p_reduced(:,2)))+20];
        rectangle('Position',pos,'EdgeColor','r','LineWidth',3)
        %             Combined = [CircleMetric(k), SquareMetric(k), TriangleMetric(k)];
        %         Txt = sprintf('C=%0.3f S=%0.3f T=%0.3f',  Combined(:));
        text(pos(1)+10,pos(2)+pos(4)+20, theShapeDistance,'Color','red','FontSize',14);
        
        
        
        
    end
    
    %     poly = polyshape(p_reduced)
    %     Peri = perimeter(poly);
    %     Area_l = polyarea(p_reduced(:,1),p_reduced(:,2));
    %
    %     if(Area_l>0.001*(rows*columns) && Peri<Area_l)
    %         if (TriangleMetric(k) < 0.6) || (SquareMetric(k)>0.9 || (CircleMetric(k) < 1.1)) %Area>0.008*(rows*columns) && Peri<Area
    %             if(length(unique(p_reduced,'row'))>=3)
    %                 pos=[ min(p_reduced(:,1))-10,min(p_reduced(:,2))-10, abs(max(p_reduced(:,1))- min(p_reduced(:,1)))+20,abs(max(p_reduced(:,2))-min(p_reduced(:,2)))+20];
    %                 rectangle('Position',pos,'EdgeColor','r','LineWidth',3)
    %                 Combined = [CircleMetric(k), SquareMetric(k), TriangleMetric(k)];
    %                 Txt = sprintf('C=%0.3f S=%0.3f T=%0.3f',  Combined(:));
    %                 text(pos(1)+10,pos(2)+pos(4)+20, theShapeDistance,'Color','red','FontSize',14);
    %
    %             end
    %         end
    %     end
    
end
bwCircularities = 0.6961
thisCircularity = 0.6961

Region of Interest Bounding Box and Shape prediction

figure_6.png

bwCircularities = 0.3348
thisCircularity = 0.3348

function [BW,maskedRGBImage] = createMask(RGB)
%createMask  Threshold RGB image using auto-generated code from colorThresholder app.
%  [BW,MASKEDRGBIMAGE] = createMask(RGB) thresholds image RGB using
%  auto-generated code from the colorThresholder app. The colorspace and
%  range for each channel of the colorspace were set within the app. The
%  segmentation mask is returned in BW, and a composite of the mask and
%  original RGB images is returned in maskedRGBImage.

% Auto-generated by colorThresholder app on 10-May-2020
%------------------------------------------------------

% Convert RGB image to chosen color space
I = rgb2hsv(RGB);

% Define thresholds for channel 1 based on histogram settings
channel1Min_red = 0.900;
channel1Max_red = 0.051;

channel1Min_yellow = 0.103;
channel1Max_yellow = 0.157;

% Define thresholds for channel 2 based on histogram settings
channel2Min = 0.590 %0.328;
channel2Max = 1.000;

% Define thresholds for channel 3 based on histogram settings
channel3Min = 0.49;
channel3Max = 1.000;

% Create mask based on chosen histogram thresholds
sliderBW = ( ((I(:,:,1) >= channel1Min_red) | (I(:,:,1) <= channel1Max_red)) | ((I(:,:,1) >= channel1Min_yellow ) & (I(:,:,1) <= channel1Max_yellow)) ) & ...
    (I(:,:,2) >= channel2Min ) & (I(:,:,2) <= channel2Max) & ...
    (I(:,:,3) >= channel3Min ) & (I(:,:,3) <= channel3Max);
BW = sliderBW;

% Initialize output masked image based on input image.
maskedRGBImage = RGB;

% Set background pixels where BW is false to zero.
maskedRGBImage(repmat(~BW,[1 1 3])) = 0;

end

function dividingValues = PlotTheoreticalCircularity()
try
    dividingValues = []; % Initialize
    fontSize = 24;
    % For reference, compute the theoretical circularity of a bunch of regular polygons with different number of sides.
    % 	fprintf('Number of Sides     Theoretical Circularity\n');
    % Define an array with the number of sides we want to compute the circularity for.
    numSides = 3 : 16;
    for k = 1 : length(numSides)
        thisSideLength = numSides(k);
        % Compute the theoretically perfect circularity, if the polygons were perfect instead of digitized.
        circularity(k) = ComputeTheoreticalCircularity(thisSideLength);
    end
    % Plot the theoretical circularities on the curve with a cross.
    % 	plot(numSides, circularity, 'b+-', 'LineWidth', 2, 'MarkerSize', 20);
    % 	grid on;
    % 	hold on;
    xl = xlim(); % Get left and right x coordinates of the graph.
    % Plot theoretical lines in dark red.
    % 	darkRed = [0.85, 0, 0];
    % 	for k = 1 : length(numSides)
    % 		% Make theoretical line on the plot in a magenta color.
    % 		line(xl, [circularity(k), circularity(k)], 'Color', darkRed, 'LineWidth', 2);
    % % 		fprintf('     %d                  %f\n', thisSideLength, circularity(k));
    % 		if k < 7 % Only print text if it's not too crowded and close together.
    % 			% Make text with the true value
    % % 			message = sprintf('Theoretical value for %d sides = %.4f', thisSideLength, circularity(k));
    % % 			text(xl(1)+0.1, circularity(k) + 0.005, message, 'Color', darkRed);
    % 		end
    % 	end
    
    % 	% Set up figure properties:
    % 	% Enlarge figure to full screen.
    % 	set(gcf, 'Units', 'Normalized', 'OuterPosition', [0 0 1 1]);
    % 	% Get rid of tool bar and pulldown menus that are along top of figure.
    % 	set(gcf, 'Toolbar', 'none', 'Menu', 'none');
    % 	% Give a name to the title bar.
    % 	set(gcf, 'Name', 'Demo by ImageAnalyst', 'NumberTitle', 'Off')
    % 	drawnow; % Make it display immediately.
    %
    % 	title('Theoretical Circularities', 'FontSize', fontSize, 'Interpreter', 'None');
    % 	xlabel('Number of Sides', 'FontSize', fontSize);
    % 	ylabel('True Circularity', 'FontSize', fontSize);
    
    % Get the midpoint between one circularity and the one for the next higher number of sides.
    dividingValues = conv(circularity, [1, 1]/2, 'valid');
    % Prepend two zeros so that we can use this array as a lookup table where we pass in
    % the number of sides as an index and it tells us the dividing value between
    % that number of sides and one more than that.
    % For example, right now dividingValues(1) gives us the dividing value between 3 and 4
    % and dividingValues(3) gives us the dividing value between 5 and 6 (instead of between 3 and 4).
    dividingValues = [0, 0, dividingValues];
    % Now dividingValues(3) will give us the dividing value between 3 and 4.
    
    % 	% Put up red horizontal lines at the dividing values
    % 	hold on;
    % 	xl = xlim();
    % 	darkGreen = [0, 0.5, 0];
    % 	for k = 1 : length(numSides)-1
    % 		thisSideLength = numSides(k);
    % 		thisDividingValue = dividingValues(thisSideLength);
    % 		h = line(xl, [thisDividingValue, thisDividingValue], 'Color', darkGreen, 'LineWidth', 2, 'LineStyle', '--');
    % 		% 		h.LineStyle = '--';
    % 		% For the first 6, print the dividing value at the left just above the line.
    % 		% After 6 it would get too crowded
    % 		if k <= 6
    % 			theLabel = sprintf('Dividing value = %.4f', thisDividingValue);
    % 			text(xl(1)+0.1, thisDividingValue + 0.005, theLabel, 'Color', darkGreen);
    % 		end
    % 	end
    
catch ME
    errorMessage = sprintf('Error in function %s() at line %d.\n\nError Message:\n%s', ...
        ME.stack(1).name, ME.stack(1).line, ME.message);
    fprintf(1, '%s\n', errorMessage);
    uiwait(warndlg(errorMessage));
end
end

% https://en.wikipedia.org/wiki/Regular_polygon
% Which says A = (1/4) * n * s^2 * cot(pi/n)
function circularity = ComputeTheoreticalCircularity(numSides)
try
    sideLength = 1;
    perimeter = numSides * sideLength;
    area = (1/4) * numSides * sideLength^2 / tan(pi / numSides);
    circularity = (4 * pi * area) / perimeter ^2;
catch ME
    errorMessage = sprintf('Error in function %s() at line %d.\n\nError Message:\n%s', ...
        ME.stack(1).name, ME.stack(1).line, ME.message);
    fprintf(1, '%s\n', errorMessage);
    uiwait(warndlg(errorMessage));
end
end

% Now compute the number of vertices by looking at the number of peaks in a plot of distance from centroid.
function numVertices = FindNumberOfVertices(blobMeasurements, labeledImage)
try
    numVertices = 0; % Initialize.
    % Get the number of blobs in the image.
    numRegions = length(blobMeasurements);
    hFig = figure;
    promptUser = true; % Let user see the curves.
    
    % For each blob, get its boundaries and find the distance from the centroid to each boundary point.
    for k = 1 : numRegions
        % Extract just this blob alone.
        thisBlob = ismember(labeledImage, k) > 0;
        % 		if promptUser % Let user see the image.
        % 			cla;
        % 			imshow(thisBlob);
        % 		end
        % Find the boundaries
        thisBoundary = bwboundaries(thisBlob);
        thisBoundary = cell2mat(thisBoundary); % Convert from cell to double.
        % Get x and y
        x = thisBoundary(:, 2);
        y = thisBoundary(:, 1);
        % Get the centroid
        xCenter = blobMeasurements(k).Centroid(1);
        yCenter = blobMeasurements(k).Centroid(2);
        % Compute distances
        distances = sqrt((x - xCenter).^2 + (y - yCenter).^2);
        % 		if promptUser % Let user see the curves.
        % 			% Plot the distances.
        % 			plot(distances, 'b-', 'LineWidth', 3);
        % 			grid on;
        % 			message = sprintf('Centroid to perimeter distances for shape #%d', k);
        % 			title(message, 'FontSize', 15);
        % 			% Scale y axis
        % 			yl = ylim();
        % 			ylim([0, yl(2)]); % Set lower limit to 0.
        % 		end
        
        % Find the range of the peaks
        peakRange = max(distances) - min(distances);
        minPeakHeight = 0.5 * peakRange;
        % Find the peaks
        
        [peakValues, peakIndexes] = findpeaks(distances, 'MinPeakProminence', minPeakHeight);
        % Find the valueys.
        [valleyValues, valleyIndexes] = findpeaks(-distances, 'MinPeakProminence', minPeakHeight);
        numVertices(k) = max([length(peakValues), length(valleyValues)]);
        % Circles seem to have a ton of peaks due to the very small range and quanitization of the image.
        % If the number of peaks is more than 10, make it zero to indicate a circle.
        if numVertices(k) > 10
            numVertices(k) = 0;
        end
        
        % 		if promptUser % Let user see the curves.
        % 			% Plot the peaks.
        % 			hold on;
        % 			plot(peakIndexes, distances(peakIndexes), 'r^', 'MarkerSize', 10, 'LineWidth', 2);
        %
        % 			% Plot the valleys.
        % 			hold on;
        % 			plot(valleyIndexes, distances(valleyIndexes), 'rv', 'MarkerSize', 10, 'LineWidth', 2);
        %
        % 			message = sprintf('Centroid to perimeter distances for shape #%d.  Found %d peaks.', k, numVertices(k));
        % 			title(message, 'FontSize', 20);
        %
        % 			% The figure un-maximizes each time when we call cla, so let's maximize it again.
        % 			% Set up figure properties:
        % 			% Enlarge figure to full screen.
        % 			set(gcf, 'Units', 'Normalized', 'OuterPosition', [0 0 1 1]);
        % 			% Get rid of tool bar and pulldown menus that are along top of figure.
        % 			set(gcf, 'Toolbar', 'none', 'Menu', 'none');
        % 			% Give a name to the title bar.
        % 			set(gcf, 'Name', 'Demo by ImageAnalyst', 'NumberTitle', 'Off')
        %
        % 			% Let user see this shape's distances plotted before continuing.
        % 			promptMessage = sprintf('Do you want to Continue processing,\nor Cancel processing?');
        % 			titleBarCaption = 'Continue?';
        % 			button = questdlg(promptMessage, titleBarCaption, 'Continue', 'Cancel', 'Continue');
        % 			if strcmpi(button, 'Cancel')
        % 				promptUser = false;
        % 			end
        % 		end
    end
    close(hFig);
catch ME
    errorMessage = sprintf('Error in function %s() at line %d.\n\nError Message:\n%s', ...
        ME.stack(1).name, ME.stack(1).line, ME.message);
    fprintf(1, '%s\n', errorMessage);
    uiwait(warndlg(errorMessage));
end
end

About

This Project was done as part of a course ECE513 Digital Image Processing at Colorado State University

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages