Writing a Frame Filter: Binarization

This example demonstrates how to create frame filters to apply binarization to live video.

It shows, how to:

The example is located in the %TOPLEVEL%\Samples\VC\Binarization directory of the IC Imaging Control installation.

Creating a Frame Filter

All frame filter implementations have to be derived from FrameFilterImpl or FrameUpdateFilterImpl. As the binarization changes every pixel of the image, it is better to implement a transform filter rather than an update filter for this purpose. FrameFilterImpl is a class template that expects the name of derived class as the template parameter:

class CBinarizationFilter
    :    public DShowLib::FrameFilterImpl<CBinarizationFilter>
{
public:
    CBinarizationFilter();

Every class derived from FrameFilterImpl has to have a static method getStaticFilterInfo that returns a FilterInfo structure, containing information about the filter:

public:
    // FrameFilterImpl implementation
    static    DShowLib::FilterInfo getStaticFilterInfo();

To create a working frame filter, the methods getSupportedInputTypes, getTransformOutputTypes and transform need to be implemented:

public:
    // IFrameFilter implementation
    virtual    void getSupportedInputTypes( DShowLib::FrameTypeInfoArray& arr ) const;
    virtual    bool getTransformOutputTypes( const DShowLib::FrameTypeInfo& in_type,
                                          DShowLib::FrameTypeInfoArray& out_types ) const;
    virtual    bool transform( const DShowLib::IFrame& src, DShowLib::IFrame& dest );

Two member variables control the binarization process. Two methods allow these member variables to be altered from the application that uses the filter:

public:
    // Enables or disabled binarizarion
    void    enable( bool bEnable );
    // Sets the threshold for the binarization
    void    setThreshold( int th );
private:
    bool    m_bEnabled;
    int        m_threshold;
};

Implementing the Frame Filter

In the constructor of CBinarizationFilter, the member variables are initialized to default values:

CBinarizationFilter::CBinarizationFilter()
    :    m_bEnabled( false ),
        m_threshold( 127 )
{
}

The getStaticFilterInfo implementation fills a FilterInfo structure with a filter name. The filterClass member is set to eFC_INTERNAL, because the filter cannot be used by generic applications such as the Filter Inspector:

FilterInfo CBinarizationFilter::getStaticFilterInfo()
{
    // Return a filter name and declare the filter as eFC_INTERNAL.
    FilterInfo fi = { L"Binarization", L"", eFC_INTERNAL };
    return fi;
}

In the getSupportedInputTypes method, a FrameTypeInfoArray is filled with frame types that the frame filter accepts as inputs. To keep things simple, only an 8-bit gray format will be allowed: eY800. When the video format of the video capture device differs from this, the image data is automatically converted before it is presented to the frame filter.

void CBinarizationFilter::getSupportedInputTypes( DShowLib::FrameTypeInfoArray& arr ) const
{
    // This filter works for 8-bit-gray images only
    arr.push_back( eY800 );
}

The getTransformOutputTypes method fills the FrameTypeInfoArray out_types with frame types that the filter can produce from a specified input frame type. Because the binarization filter does not change the frame type or size, we just insert the input type into that array:

bool CBinarizationFilter::getTransformOutputTypes( const DShowLib::FrameTypeInfo& in_type,
                                                   DShowLib::FrameTypeInfoArray& out_types ) const
{
    // We don't change the image type, output = input
    out_types.push_back( in_type );
    return true;
}

The transform method is called for every frame. It is responsible to perform the binarization, according to the current settings.

Because the transform method runs in a different thread from the user interface, access to the member variables m_bEnabled and m_threshold from parallel calls to setThreshold or enable has to be protected.

First, IFrameFilter::beginParamTransfer is called. Internally, this method enters a critical section. When a user of the frame filter wants to set parameters, he/she has to call beginParamTransfer, too. IFrameFilter::endParamTransfer leaves the critical section. The values of the member variables are copied to local stack variables, so that they remain constant for the whole transformation.

The binarization itself is a simple operation: Compare each pixel's gray value to the threshold. If the gray value is greater than the threshold, set the pixel to the maximum gray value, otherwise set it to zero.

bool CBinarizationFilter::transform( const DShowLib::IFrame& src, DShowLib::IFrame& dest )
{
    // Check whether the destination frame is available
    if( dest.getPtr() == 0 ) return false;
    BYTE* pIn = src.getPtr();
    BYTE* pOut = dest.getPtr();
    // Copy the member variables to the function's stack, to protect them from being
    // overwritten by parallel calls to setThreshold() etc.
    //
    // beginParamTransfer/endParamTransfer makes sure that the values from various
    // member variables are consistent, because the user of this filter must enclose
    // writing parameter access into beginParamTransfer/endParamTransfer, too.
    beginParamTransfer();
    bool enabled = m_bEnabled;
    int threshold = m_threshold;
    endParamTransfer();
    // Check whether binarization is enabled
    if( enabled )
    {
        // For each byte in the input buffer, check whether it is greater or
        // equal to the threshold.
        int bufferSize = src.getFrameType().buffersize;
        while( bufferSize-- > 0 )
        {
            if( *pIn++ >= threshold )
            {
                *pOut++ = 255;
            }
            else
            {
                *pOut++ = 0;
            }
        }
    }
    else
    {
        // Binarization is disabled: Copy the image data without modifying it.
        memcpy( pOut, pIn, src.getFrameType().buffersize );
    }
    return true;
}

The methods enable and setThreshold change the filter behavior:

void CBinarizationFilter::enable( bool bEnable )
{
    m_bEnabled = bEnable;
}

void CBinarizationFilter::setThreshold( int th )
{
    m_threshold = th;
}

Using the Frame Filter

We create an instance of CBinarizationFilter as a member variable of the dialog class: m_binarizationFilter.

When the Grabber object is initialized, Grabber::setDeviceFrameFilters is called to set our frame filter:

// Set the live video output window
m_Grabber.setHWND( GetDlgItem( IDC_DISPLAY )->GetSafeHwnd() );
// Disable overlay bitmap
m_Grabber.setOverlayBitmapPathPosition( ePP_NONE );
// Set the binarization filter as device frame filter
m_Grabber.setDeviceFrameFilters( &m_binarizationFilter );
// Show device settings dialog
m_Grabber.showDevicePage( GetSafeHwnd() );
// If a device was selected, start live mode.
if( m_Grabber.isDevValid() )
{
    m_Grabber.startLive();
}

When live mode is started, all images coming from the video capture device are transformed by our frame filter.

Accessing Frame Filter Parameters

In the event handler for the check box that enables or disables the binarization, CBinarizationFilter::enable is called.

All calls to methods modifying filter parameters have to be enclosed by IFrameFilter::beginParamTransfer and IFrameFilter::endParamTransfer.

void CBinarizationDlg::OnBnClickedBinarizeEnable()
{
    // Read current checked state
    bool bEnable = m_BinarizeEnable.GetCheck() == BST_CHECKED;
    // Enclose frame filter parameter access into beginParamTransfer/endParamTransfer,
    // to get consistent values.
    m_binarizationFilter.beginParamTransfer();
    m_binarizationFilter.enable( bEnable );
    m_binarizationFilter.endParamTransfer();
    // Set threshold slider availability
    m_BinarizeThreshold.EnableWindow( bEnable );
    m_BinarizeThresholdStatic.EnableWindow( bEnable );
}

In the event handler for the threshold slider, CBinarizationFilter::setThreshold is called.

This call also has to be enclosed by IFrameFilter::beginParamTransfer and IFrameFilter::endParamTransfer.

void CBinarizationDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    // Read current slider position
    int th = m_BinarizeThreshold.GetPos();
    // Enclose frame filter parameter access into beginParamTransfer/endParamTransfer,
    // to get consistent values.
    m_binarizationFilter.beginParamTransfer();
    m_binarizationFilter.setThreshold( th );
    m_binarizationFilter.endParamTransfer();
    // Display the threshold in the static window.
    wchar_t buf[20];
#if _MSC_VER >= 1400
    _itow_s( th, buf, 10 ) ;
#else
    _itow( th, buf, 10 );
#endif
    m_BinarizeThresholdStatic.SetWindowText( buf );
    CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}

<< Programmer's Guide