Contrast Adjustment

This example shows how to generate HDL code from a MATLAB® design that adjusts image contrast by linearly scaling pixel values.

Algorithm

Contrast adjustment adjusts the contrast of an image by linearly scaling the pixel values between upper and lower limits. Pixel values that are above or below this range are saturated to the upper or lower limit value, respectively.

MATLAB Design

design_name = 'mlhdlc_image_scale';
testbench_name = 'mlhdlc_image_scale_tb';

Let us take a look at the MATLAB design

type(design_name);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% scale.m
%
% Adjust image contrast by linearly scaling pixel values. 
%
% The input pixel value range has 14bits and output pixel value range is
% 8bits.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [x_out, y_out, pixel_out] = ...
    mlhdlc_image_scale(x_in, y_in, pixel_in, ...
          damping_factor_in, dynamic_range_in, ...
          tail_size_in, max_gain_in, ...
          width, height)

%   Copyright 2011-2015 The MathWorks, Inc.

persistent histogram1 histogram2
persistent low_count
persistent high_count
persistent offset
persistent gain
persistent limits_done
persistent damping_done
persistent reset_hist_done
persistent scaling_done
persistent hist_ind
persistent tail_high
persistent min_hist_damped    %Damped lower limit of populated histogram
persistent max_hist_damped    %Damped upper limit of populated histogram
persistent found_high
persistent found_low

DR_PER_BIN          = 8;
SF                  = 1./(1:(2^14/8)); % be nice to fix this
NR_OF_BINS          = (2^14/DR_PER_BIN) - 1;
MAX_DF              = 255;

if isempty(offset)
    offset               = 1;
    gain                 = 1;
    limits_done          = 1;
    damping_done         = 1;
    reset_hist_done      = 1;
    scaling_done         = 1;
    hist_ind             = 1;
    tail_high            = NR_OF_BINS;
    low_count 			 = 0;
    high_count 			 = 0;
    min_hist_damped      = 0;
    max_hist_damped      = (2^14/DR_PER_BIN) - 1;
    found_high           = 0;
    found_low            = 0;
end
if isempty(histogram1)
    histogram1           = zeros(1, NR_OF_BINS+1);
    histogram2           = zeros(1, NR_OF_BINS+1);
end

if y_in < height
    frame_valid = 1;
    if x_in < width
        line_valid = 1;
    else
        line_valid = 0;
    end
else
    frame_valid = 0;
    line_valid = 0;
end

% initialize at beginning of frame
if x_in == 0 && y_in == 0
    limits_done = 0;
    damping_done = 0;
    reset_hist_done = 0;
    scaling_done = 0;
    low_count = 0;
    high_count = 0;
    hist_ind = 1;
end

max_gain_frac = max_gain_in/2^4;
pix11 = floor(pixel_in/DR_PER_BIN);
pix_out_temp = pixel_in;

%**************************************************************************
%Check if valid part of frame. If pixel is valid remap pixel to desired
%output dynamic range (dynamic_range_in) by subtracting the damped offset
%(min_hist_damped) and applying the calculated gain calculated from the
%previous frame histogram statistics.
%**************************************************************************
% histogram read
histReadIndex1 = 1;
histReadIndex2 = 1;
if frame_valid && line_valid
    histReadIndex1 = pix11+1;
    histReadIndex2 = pix11+1;
elseif ~limits_done
    histReadIndex1 = hist_ind;
    histReadIndex2 = NR_OF_BINS - hist_ind;
end
histReadValue1 = histogram1(histReadIndex1);
histReadValue2 = histogram2(histReadIndex2);
histWriteIndex1 = NR_OF_BINS+1;
histWriteIndex2 = NR_OF_BINS+1;
histWriteValue1 = 0;
histWriteValue2 = 0;
if frame_valid
    if line_valid
        temp_sum = histReadValue1 + 1;
        ind = min(pix11+1, NR_OF_BINS);
        val = min(temp_sum, tail_size_in);
        histWriteIndex1 = ind;
        histWriteValue1 = val;
        histWriteIndex2 = ind;
        histWriteValue2 = val;
        
        %Scale pixel
        pix_out_offs_corr = pixel_in - min_hist_damped*DR_PER_BIN;
        pix_out_scaled = pix_out_offs_corr * gain;
        pix_out_clamp = max(min(dynamic_range_in, pix_out_scaled), 0);
        pix_out_temp = pix_out_clamp;
    end
else
    %**********************************************************************
    %Ignore tail_size_in pixels and find lower and upper limits of the
    %histogram.
    %**********************************************************************
    if ~limits_done
        if hist_ind == 1
            tail_high = NR_OF_BINS-1;
            offset = 1;
            found_high = 0;
            found_low = 0;
        end
        
        low_count = low_count + histReadValue1;
        hist_ind_high = NR_OF_BINS - hist_ind;
        high_count = high_count + histReadValue2;
        
        %Found enough high outliers
        if high_count > tail_size_in && ~found_high
            tail_high = hist_ind_high;
            found_high = 1;
        end
        
        %Found enough low outliers
        if low_count > tail_size_in && ~found_low
            offset = hist_ind;
            found_low = 1;
        end
        
        hist_ind = hist_ind + 1;
        %All bins checked so limits must already be found
        if hist_ind >= NR_OF_BINS
            hist_ind = 1;
            limits_done = 1;
        end
        %**********************************************************************
        %Damp the limit change to avoid image flickering. Code below equivalent
        %to: max_hist_damped = damping_factor_in*max_hist_dampedOld +
        %(1-damping_factor_in)*max_hist_dampedNew;
        %**********************************************************************
    elseif ~damping_done
        min_hist_weighted_old = damping_factor_in*min_hist_damped;
        min_hist_weighted_new = (MAX_DF-damping_factor_in+1)*offset;
        min_hist_weighted = (min_hist_weighted_old + ...
            min_hist_weighted_new)/256;
        min_hist_damped = max(0, min_hist_weighted);
        max_hist_weighted_old = damping_factor_in*max_hist_damped;
        max_hist_weighted_new = (MAX_DF-damping_factor_in+1)*tail_high;
        max_hist_weighted = (max_hist_weighted_old + ...
            max_hist_weighted_new)/256;
        max_hist_damped = min(NR_OF_BINS, max_hist_weighted);
        damping_done = 1;
        hist_ind = 1;
        %**********************************************************************
        %Reset all bins to zero. More than one bin can be reset per function
        %call if blanking time is too short.
        %**********************************************************************
    elseif ~reset_hist_done
        histWriteIndex1 = hist_ind;
        histWriteValue1 = 0;
        histWriteIndex2 = hist_ind;
        histWriteValue2 = 0;
        hist_ind = hist_ind+1;
        if hist_ind == NR_OF_BINS
            reset_hist_done = 1;
        end
        %**********************************************************************
        %The gain factor is determined by comparing the measured damped actual
        %dynamic range to the desired user specified dynamic range. Input
        %dynamic range is measured in bins over DR_PER_BIN space.
        %**********************************************************************
    elseif ~scaling_done
        dr_in = round(max_hist_damped - min_hist_damped);
        gain_temp = dynamic_range_in*SF(dr_in);
        gain_scaled = gain_temp/DR_PER_BIN;
        gain = min(max_gain_frac, gain_scaled);
        scaling_done = 1;
        hist_ind = 1;
    end
end
histogram1(histWriteIndex1) = histWriteValue1;
histogram2(histWriteIndex2) = histWriteValue2;

x_out = x_in;
y_out = y_in;
pixel_out = pix_out_temp;
type(testbench_name);
%Test bench for scaling, analogous to automatic gain control (AGC)

%   Copyright 2011-2018 The MathWorks, Inc.

testFile = 'mlhdlc_img_peppers.png';
imgOrig = imread(testFile);
[height, width] = size(imgOrig);
imgOut = zeros(height,width);
hBlank = 20;
% make sure we have enough vertical blanking to filter the histogram
vBlank = ceil(2^14/(width+hBlank));

%df - Temporal damping factor of rescaling
%dr - Desired output dynamic range
df = 0; 
dr = 255; 
nrOfOutliers = 248;
maxGain = 2*2^4;

for frame = 1:2
    disp(['frame: ', num2str(frame)]);
    for y_in = 0:height+vBlank-1       
        %disp(['frame: ', num2str(frame), ' of 2, row: ', num2str(y_in)]);
        for x_in = 0:width+hBlank-1
            if x_in < width && y_in < height
                pixel_in = double(imgOrig(y_in+1, x_in+1));
            else
                pixel_in = 0;
            end
            
            [x_out, y_out, pixel_out] = ...
                mlhdlc_image_scale(x_in, y_in, pixel_in, df, dr, ...
                    nrOfOutliers, maxGain, width, height);
                       
            if x_out < width && y_out < height
                imgOut(y_out+1,x_out+1) = pixel_out;
            end
        end
    end
    
    figure('Name', [mfilename, '_scale_plot']);
    imgOut = round(255*imgOut/max(max(imgOut)));
    subplot(2,2,1); imshow(imgOrig, []);
    title('Original Image');
    subplot(2,2,2); imshow(imgOut, []);
    title('Scaled Image');
    subplot(2,2,3); histogram(double(imgOrig(:)),2^14-1);
    axis([0, 255, 0, 1500]);
    title('Histogram of original Image');
    subplot(2,2,4); histogram(double(imgOut(:)),2^14-1);
    axis([0, 255, 0, 1500]);
    title('Histogram of equalized Image');
end

Simulate the Design

It is a good practice to simulate the design with the testbench prior to code generation to make sure there are no runtime errors.

mlhdlc_image_scale_tb
frame: 1
frame: 2

Setup for the Example

Execute the following lines of code to copy the necessary example files into a temporary folder.

mlhdlc_demo_dir = fullfile(matlabroot, 'toolbox', 'hdlcoder', 'hdlcoderdemos', 'matlabhdlcoderdemos');
mlhdlc_temp_dir = [tempdir 'mlhdlc_scale'];

% create a temporary folder and copy the MATLAB files
cd(tempdir);
[~, ~, ~] = rmdir(mlhdlc_temp_dir, 's');
mkdir(mlhdlc_temp_dir);
cd(mlhdlc_temp_dir);

% copy files to the temp dir
copyfile(fullfile(mlhdlc_demo_dir, [design_name,'.m*']), mlhdlc_temp_dir);
copyfile(fullfile(mlhdlc_demo_dir, [testbench_name,'.m*']), mlhdlc_temp_dir);
copyfile(fullfile(mlhdlc_demo_dir, 'mlhdlc_img_peppers.png'), mlhdlc_temp_dir);

Create a New HDL Coder™ Project

coder -hdlcoder -new mlhdlc_scale_prj

Next, add the file 'mlhdlc_image_scale.m' to the project as the MATLAB Function and 'mlhdlc_image_scale_tb.m' as the MATLAB Test Bench.

Refer to Getting Started with MATLAB to HDL Workflow for a more complete tutorial on creating and populating MATLAB HDL Coder projects.

Run Fixed-Point Conversion and HDL Code Generation

Launch the Workflow Advisor from the Build tab and right click on the 'Code Generation' step and choose the option 'Run to selected task' to run all the steps from the beginning through the HDL code generation.

Examine the generated HDL code by clicking on the hyperlinks in the Code Generation Log window.

Clean up the Generated Files

You can run the following commands to clean up the temporary project folder.

mlhdlc_demo_dir = fullfile(matlabroot, 'toolbox', 'hdlcoder', 'hdlcoderdemos', 'matlabhdlcoderdemos');
mlhdlc_temp_dir = [tempdir 'mlhdlc_scale'];
clear mex;
cd (mlhdlc_demo_dir);
rmdir(mlhdlc_temp_dir, 's');