chessboard_calibration.m 7.57 KiB
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% dotboard_calibration.m
% find markers in images of calibration plates
% then perform a 2D calibration to dewarp the calibration plate image
addpath([pwd '/../minmax/']);
addpath([pwd '/../pinhole/']);
addpath([pwd '/../misc/']);
%% input parameters
grid_dx = -1; % real units, mm
grid_dy = +1;
invert_image = 0; % invert image for black dots on white
blur_image = 0; % gaussian blur image
LoG_image = 0; % apply laplacian of Gaussian filter
checkerboard_image = 0; % apply checkerboard image filter
median_filter = 1;
median_filter_size = [5 5];
normalise_image = 1;
corner_metric = 0.1; % threshold for identifying corners
dot_half_window = [64 64];
output_dir = 'E:\mixer\lpt\calibration\21-06-14\';
input_file_list = {'E:\mixer\lpt\calibration\21-06-14\C15794_im02.tif'};
plate_origin = [0 0 0]';
camera_index = 1;
camera_id = 1;
calib_idx_offset = 0;
origin_pos = [0 0];
image_clim = [0 255];
%% generate calibration for each image
nCalibrations = length(input_file_list);
calib_idx = 1;
while calib_idx <= nCalibrations
%% update user of status
input_file = input_file_list{calib_idx};
output_file = sprintf('%s/CAM%d-%02d.mat', output_dir, camera_index, calib_idx+calib_idx_offset);
fprintf('Image %02d of %02d:\n', calib_idx, nCalibrations);
% check if calibration already exists
if exist(output_file, 'file')
sInput = input('Calibration already exists - overwrite? y to overwrite: ', 's');
if ~strcmpi(sInput, 'y')
calib_idx = calib_idx + 1;
continue;
end
end
% check if image input exists
if ~exist(input_file, 'file')
fprintf(' - could not find image file\n');
end
%% load image
im = readimg(input_file);
im = double((im{1}'));
Nx = size(im, 1);
Ny = size(im, 2);
i_axis = 0 : Nx-1;
j_axis = 0 : Ny-1;
%% create filtered image for processing
im_filt = im;
if invert_image
im_filt = max(im(:))-im;
end
if blur_image
G = fspecial('gaussian', [5 5]);
im_filt = filter2(G, im_filt);
end
if LoG_image
scale = norm(dot_half_window)*0.2;
G = fspecial('log', 2*dot_half_window, scale);
im_filt = scale * conv2(G, im_filt);
end
if median_filter
im_filt = medfilt2(im_filt, median_filter_size);
end
if checkerboard_image
G = repelem(eye(2), dot_half_window(1), dot_half_window(2));
im_filt = conv2(G, im_filt);
end
if normalise_image
[minv, maxv] = minmaxfiltnd(single(im_filt), dot_half_window*2+1);
im_filt = (im_filt - minv) ./ max(16, maxv-minv);
end
%% display image
figure(1);
clf;
hold off;
ax=gca;
h_filt = imagesc(im_filt');
axis equal tight xy;
colormap gray;
hold on;
colorbar;
%% crop image
fprintf('Select the bottom left square of the board:\n');
im_bl = ginput(1);
plot(im_bl(1), im_bl(2), 'ro');
fprintf('Select the bottom right square of the board:\n');
im_br = ginput(1);
plot(im_br(1), im_br(2), 'ro');
fprintf('Select the top right square of the board:\n');
im_tr = ginput(1);
plot(im_tr(1), im_tr(2), 'ro');
fprintf('Select the top left square of the board:\n');
im_tl = ginput(1);
plot(im_tl(1), im_tl(2), 'ro');
% identify corners
im_corners = [im_bl; im_tr];
im_corners = sort(round(im_corners), 1);
%% coordinate directions
im_ex = im_br + im_tr - im_bl - im_tl;
im_ey = im_tr + im_tl - im_br - im_bl;
im_ex = im_ex' / norm(im_ex);
im_ey = im_ey' / norm(im_ey);
%% display cropped and filtered image
plot(ax(1), im_corners([1 2 2 1 1], 1), im_corners([1 1 2 2 1], 2), 'r-');
rx = i_axis > im_corners(1,1) & i_axis < im_corners(2,1);
ry = j_axis > im_corners(1,2) & j_axis < im_corners(2,2);
% crop filtered image
im_crop = zeros(Nx,Ny);
im_crop(rx,ry) = im_filt(rx,ry);
im_filt = im_crop;
set(h_filt, 'cdata', im_filt');
drawnow;
%% extract markers using chessboard algorithm
h_markers = [];
h_text = [];
while ~isnan(corner_metric)
%% search for chessboard corners
fprintf('searching for checkerboard corners with m=%0.3f ...\n', corner_metric);
[ij_markers, board_size] = detectCheckerboardPoints(im_filt', 'MinCornerMetric', corner_metric);
ij_markers = ij_markers'; % 2 x n_markers
%% update figure or try again
if isempty(ij_markers)
fprintf('no valid markers found.\n');
fprintf('try again with a different threshold\n\n');
else
%% display
figure(1);
hold on;
delete(h_markers);
delete(h_text);
h_markers = plot(ax(1), ij_markers(1,:), ij_markers(2,:), 'g+');
end
%% prompt for new threshold
ret = input('enter a new corner metric or nan to accept: ', 's');
corner_metric = str2double(ret);
end
%% get coordinates of markers
% create grid
x_grid = (1 : board_size(2)-1) * grid_dx;
y_grid = (1 : board_size(1)-1) * grid_dy;
x_grid = x_grid - round(mean(x_grid));
y_grid = y_grid - round(mean(y_grid));
[xm, ym] = meshgrid(x_grid, y_grid);
xy_markers = [xm(:) ym(:)]';
n_markers = prod(board_size-1);
% annotate
h_text = [];
delete(findobj(gcf,'type','text'));
for k = [1 board_size(1)-1 n_markers n_markers-board_size(1)+2]
str = sprintf('(%0.1f,%0.1f)', xy_markers(:,k));
h_text(k) = text(ij_markers(1,k), ij_markers(2,k), str, 'color', 'b', ...
'fontsize', 18, ...
'horizontalalignment', 'center', 'verticalalignment', 'middle');
end
%% create calibration and dewarp
xy_tilde = [xy_markers; ones(1,n_markers)];
P2D = dlt33([ij_markers;ones(1,n_markers)], xy_tilde);
ij_tilde = P2D * xy_tilde;
lambda = [1; 1] * ij_tilde(3,:);
ij_fit = ij_tilde(1:2,:) ./ lambda;
ij_error = ij_markers - ij_fit;
px_error = sqrt(ij_error(1,:).^2 + ij_error(2,:).^2);
rms_error = sqrt(mean(px_error.^2));
fprintf('RMS fit error: %0.2f px\n', rms_error);
%% work out x and y axes
x_lim = sort(x_grid([1 end]));
y_lim = sort(y_grid([1 end]));
x_axis = linspace(x_lim(1), x_lim(2), Nx);
y_axis = linspace(y_lim(1), y_lim(2), Ny);
% calculate dewarping
[xmat, ymat] = ndgrid(x_axis, y_axis);
ij_tilde_dewarped = P2D * [xmat(:) ymat(:) ones(Nx*Ny,1)]';
lambda = ij_tilde_dewarped(3,:);
imat = reshape(ij_tilde_dewarped(1,:) ./ lambda, [Nx Ny]);
jmat = reshape(ij_tilde_dewarped(2,:) ./ lambda, [Nx Ny]);
im_xy = interp2(i_axis, j_axis, im', imat, jmat, 'linear');
%% show dewarped image
figure(2);
hold off;
imagesc(x_axis, y_axis, im_xy');
xlim(x_lim);
ylim(y_lim);
colormap gray;
axis xy equal tight;
hold on;
%set(gca,'Clim', image_clim);
for x = (round(x_lim(1)/abs(grid_dx)) : round(x_lim(end)/abs(grid_dx))) * abs(grid_dx)
plot([x x], [y_axis(1) y_axis(end)], 'r-');
end
for y = (round(y_lim(1)/abs(grid_dy)) : round(y_lim(end)/abs(grid_dy))) * abs(grid_dy)
plot([x_axis(1) x_axis(end)], [y y], 'r-');
end
%% query user to save calibration or repeat
sInput = input('Would you like to repeat the calibration? y to repeat: ', 's');
if strcmpi(sInput, 'y')
continue; % continue without incrementing the counter
end
%% save output
fprintf('Saving ... ');
fstruct = struct('source_image', im, ...
'dewarped_image', im_xy, ...
'plate_pos', plate_origin(:, calib_idx), ...
'P', P2D, ...
'i_axis', i_axis, 'j_axis', j_axis, ...
'x_axis', x_axis, 'y_axis', y_axis, 'x_lim', x_lim, 'y_lim', y_lim, ...
'grid_dx', grid_dx, 'grid_dy', grid_dy, ...
'ij_markers', ij_markers, 'xy_markers', xy_markers, ...
'camera_index', camera_index, ...
'id', camera_id);
save(output_file, '-struct', 'fstruct');
fprintf('done\n');
calib_idx = calib_idx + 1;
end