Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Example: Hello World

Luke Iwanski edited this page Sep 19, 2016 · 1 revision

This page is to give a brief introduction of a Hello World for VisionCpp. VisionCpp is a high-level computer vision framework that supports the development of performance portable computer vision applications running on OpenCL-accelerated platforms. Taking advantage of SYCL's "single-source programming style", VisionCpp allows programmers to easily develop custom vision operations in C++ and maintain type safety across host CPU and accelerator.
The generated application will then be portable to different platforms with no modification to the source code and with good performance.

##Hello World Example To see the whole code of this example: link

This "Hello World" creates a simple example converting RGB pixels to a greyscale pixel which is a normalised float value pixel. As input we receive a 3-channel RGB pixel. We normalise it to a 3-channel float value where each element is between 0 and 1. At the end we apply a greyscale formula to convert the pixel to grey.

This example covers some basic concepts of VisionCpp which is important in the development of more high-level applications.

  • Tree-based Model:
    • VisionCpp uses a tree-based model (the "front-end") to provide a DSEL for computer vision applications on different architectures (or "back-ends"). Using a tree-based model, each node of the tree receives its input(s) from its child(ren), applies its operation, and passes the result to its parent. This model hides the communication and coordination among the nodes of the tree, as well as allowing kernel fusion and other types of optimization.
  • Selector:
    • The first thing to do in VisionCpp is to select the OpenCL-accelerator device. Depending on your machine you can select cpu or gpu.
// where VisionCpp will run.
auto dev = visioncpp::make_device<visioncpp::backend::sycl, visioncpp::device::cpu>();
  • Terminal nodes
    • A "terminal node" is used to define the input/output data structure. First we need to initialize our host memory.
// create a host container with our input RGB data
std::shared_ptr<unsigned char> in_rgb(new unsigned char[3], [](unsigned char* dataMem) { delete[] dataMem; });

// create a host container for GREY output data
std::shared_ptr<float> out_grey(new float[1], [](float* dataMem) { delete[] dataMem; });
  • Now we can create our terminal node which will receive the data from host container.
// create terminal nodes - the leaf nodes in the expression tree.
// terminal struct takes 4 arguments
// 1st template parameter specifies the data U8 (unsigned char) C3 (three channels)
// 2nd number of columns in the storage
// 3rd number of rows in the storage
// 4th underlying storage type - currently only Buffer2D supported
auto data = visioncpp::terminal<visioncpp::pixel::U8C3, 1, 1, visioncpp::memory_type::Buffer2D>(in_rgb.get());
auto data_out = visioncpp::terminal<visioncpp::pixel::F32C1, 1, 1, visioncpp::memory_type::Buffer2D>(out_grey.get());
  • Non-Terminal Node: A non-terminal node represents a computer vision operation applied on an input. In this example we are going to see the point_operation node which operates over all pixels.
    • Functor: The functor are operations created by the user to be executed in the non-terminal nodes.
      • Normalise Functor: First we need to create the functor to normalize the values between 0...1.
/// \brief This functor performs conversion from [0, 255] to [0.0f, 1.0f]
struct MyNormaliseFunctor {
  /// \param in - three channel unsigned char
  /// \return F32C3 - three channel float
  visioncpp::pixel::F32C3 operator()(visioncpp::pixel::U8C3 in) {
    const float FLOAT_TO_BYTE = 255.0f;
    const float BYTE_TO_FLOAT = 1.0f / FLOAT_TO_BYTE;
    return visioncpp::pixel::F32C3(static_cast<float>(in[0] * BYTE_TO_FLOAT),
                                   static_cast<float>(in[1] * BYTE_TO_FLOAT),
                                   static_cast<float>(in[2] * BYTE_TO_FLOAT));
  }
};
  • Convert to Grey Functor: This functor converts RGB data into GREY
/// \brief This functor performs RGB to grey convertion following rule:
/// GREY <- 0.299f * R + 0,587f * G + 0.114 * B
struct MyGreyFunctor {
  /// \param in - RGB pixel.
  /// \returns float - greyscale value.
  float operator()(visioncpp::pixel::F32C3 in) {
    return 0.299f * in[0] + 0.587f * in[1] + 0.114f * in[2];
  }
};
  • With the functors already created, we can create our non-terminal nodes.
// converting unsigned char value to normalised float value
auto node = visioncpp::point_operation<MyNormaliseFunctor>(data);
// float RGB to float Grey conversion
auto node2 = visioncpp::point_operation<MyGreyFunctor>(node);
  • The last step of the tree creation is to assign non-terminal node to a terminal node.
// assign operation that writes output of the pipe to output terminal node
auto pipe = visioncpp::assign(data_out, node2);
  • After the tree is created we can execute it.
// execute the pipe defined pipe
// 1st template parameter defines if VisionCpp back-end fuses the expression
// 2nd & 3rd shared memory sizes ( column, row )
// 4th & 5th local work group size ( column , row )
visioncpp::execute<visioncpp::policy::Fuse, 1, 1, 1, 1>(pipe, dev);
  • After it is computed we can access the date in our out_grey host data created
printf("RGB: %u %u %u \nGrey: %f \n", in_rgb.get()[0], in_rgb.get()[1], in_rgb.get()[2], out_grey.get()[0]);

##Summary: A summary what was done that it is need to be done in all VisionCpp applications.

  • Decide the backend device.
  • Initialize the host container.
  • Create terminal nodes which will receive a pointer from the host container.
  • Create functors to be used by non-terminal nodes.
  • Create the tree your computer vision algorithm.
  • Assign non-terminal node to a terminal node.
  • Execute tree.