Video Histogram

Demo to show live histogram of video, both 1D histograms of RGB channels and 2D histogram of Hue-Saturation.

Sources:

function varargout = color_histogram_demo_gui(varargin)
    % setup video source
    vid = createVideoCapture([], 'chess');
    pause(0.5);
    assert(vid.isOpened());

    % build and initialize GUI
    opts = default_options();
    h = buildGUI(vid, opts);
    if nargout > 0, varargout{1} = h; end

    % main loop: while UI still open, keep processing frames
    while all(ishghandle(h.img)) && vid.isOpened()
        % grab next frame
        frame = vid.read();
        if isempty(frame), break; end

        % compute 2D Hue/Saturation histogram and 1D R/G/B histogram
        opts.scale = get(h.slid, 'Value');  % current slider value
        out2 = hist2D_HSV(frame, opts);
        out1 = hist1D_RGB(frame, opts);

        % show results
        set(h.lbl, 'String',sprintf('Scale: %.0f',opts.scale));
        set(h.img(1), 'CData',frame);
        set(h.img(2), 'CData',out2);
        set(h.img(3), 'CData',out1);
        drawnow;
    end
    vid.release();
end

function opts = default_options()
    %DEFAULT_OPTIONS  Create structure of options

    opts = struct();

    % hue/saturation histogram params (hue: 0-179 degrees, saturation: 0-255)
    opts.hlims = [0 180];
    opts.slims = [0 256];
    opts.hbins = 180;
    opts.sbins = 256;

    % R/G/B histograms params (R/G/B: 0-255)
    opts.clims = [0 256];
    opts.cbins = 256;
    opts.sz = [150, 350];

    % build HSV map, used to color the histogram
    [H,S,V] = ndgrid((1:opts.hbins)-1, (1:opts.sbins)-1, 255);
    RGB = cv.cvtColor(uint8(cat(3,H,S,V)), 'HSV2RGB');
    opts.map = double(RGB)./255;
    opts.scale = 20;
end

function out = hist2D_HSV(img, opts)
    %HIST1D_HSV  Calculate H-S 2D histogram

    % downscale for faster computation
    if false
        img = cv.pyrDown(img);
    end

    % convert RGB to HSV colorspace
    hsv = cv.cvtColor(img, 'RGB2HSV');

    % blacken dark pixels
    mask = hsv(:,:,3) < 20;  % V between 0 and 180
    mask = repmat(mask, [1 1 3]);
    hsv(mask) = 0;

    % calculate 2D histogram over H and S channels
    H = cv.calcHist(hsv, {opts.hlims, opts.slims}, 'Channels',[0 1], ...
        'Uniform',true, 'HistSize',[opts.hbins opts.sbins]);

    % scale histogram for better visualization
    H = log(H) * (opts.scale/64);  % log-transform and scale
    H = min(max(H, 0), 1);         % clip to [0,1] range

    % color the histogram using the HSV map (both in 0-1 range)
    out = bsxfun(@times, H, opts.map);
end

function out = hist1D_RGB(img, opts)
    %HIST1D_HSV  Calculate RGB 1D histograms

    % output image
    out = zeros([opts.sz 3], 'uint8');

    % x-coords of histogram line points scaled to fit image horizontally
    % (origin is top-right corner)
    x = opts.sz(2) * (1:opts.cbins) / opts.cbins;

    % for each channel in R,G,B
    clrs = [255 0 0; 0 255 0; 0 0 255];
    for i=1:3
        % compute 1D histogram for current channel
        H = cv.calcHist(img(:,:,i), {opts.clims}, ...
            'HistSize',opts.cbins, 'Uniform',true);

        % normalize counts to [0,opts.sz(1)] to fit image vertically
        if true
            H = cv.normalize(H, 'NormType','MinMax', 'Alpha',0, 'Beta',opts.sz(1));
        else
            H = opts.sz(1) * (H - min(H)) / (max(H) - min(H));
        end

        % draw histogram as line
        out = cv.polylines(out, [x(:), opts.sz(1) - H(:)], ...
            'Color',clrs(i,:), 'Closed',false);
    end
end

function onType(~,e,h)
    %ONTYPE  Event handler for key press on figure

    switch e.Key
        case {'q', 'escape'}
            close(h.fig);
    end
end

function h = buildGUI(vid, opts)
    %BUILDGUI  Construct the GUI

    % structure of handles
    h = struct();

    % video feed: figure, axis, image
    if true
        sz = [vid.get('FrameHeight'), vid.get('FrameWidth')];
        img = zeros([sz 3], 'uint8');
    else
        img = vid.read();
        assert(~isempty(img));
        sz = size(img);
    end
    h.fig(1) = figure('Name','Camera', 'Menubar','none', 'Resize','on', ...
        'Position',[100 200 sz(2) sz(1)]);
    h.ax(1) = axes('Parent',h.fig(1), 'Units','normalized', 'Position',[0 0 1 1]);
    h.img(1) = imshow(img, 'Parent',h.ax(1));

    % Hue/Saturation 2D histogram: figure, axis, image, slider, label
    img = zeros(size(opts.map), 'double');
    h.fig(2) = figure('Name','Hue/Saturation Hist', 'Menubar','none', 'Resize','off', ...
        'Position',[100+sz(2)+50 200 opts.sbins opts.hbins+21]);
    h.ax(2) = axes('Parent',h.fig(2), 'Units','pixels', ...
        'Position',[1 1 opts.sbins opts.hbins]);
    h.img(2) = imshow(img, 'Parent',h.ax(2));
    h.slid = uicontrol('Parent',h.fig(2), 'Style','slider', ...
        'Position',[70 opts.hbins+1 opts.sbins-75 20], ...
        'Value',opts.scale, 'Min',1, 'Max',64, 'SliderStep',[1 5]./64);
    h.lbl = uicontrol('Parent',h.fig(2), 'Style','text', ...
        'BackgroundColor',get(h.fig(1), 'Color'), ...
        'String',sprintf('Scale: %.0f',opts.scale), ...
        'Position',[10 opts.hbins+1 60 20]);

    % RGB histogram (three 1D hist): figure, axis, image
    img = zeros([opts.sz 3], 'uint8');
    h.fig(3) = figure('Name', 'R/G/B Hist', 'Menubar','none', 'Resize','on', ...
        'Position',[100+sz(2)+50+opts.sbins+50 200 opts.sz(2) opts.sz(1)]);
    h.ax(3) = axes('Parent',h.fig(3), 'Units','normalized', 'Position',[0 0 1 1]);
    h.img(3) = imshow(img, 'Parent',h.ax(3));

    % hook event handlers
    set(h.fig, 'WindowKeyPressFcn',{@onType,h}, ...
        'CloseRequestFcn',@(~,~) delete(h.fig), ...
        'Interruptible','off', 'BusyAction','cancel');
end