Run-Time Specified Images and Image Views
运行时特定图片和图片视图
So far we have created a generic function that computes the image gradient of a templated image view. Sometimes, however, the properties of an image view, such as its color space and channel depth, may not be available at compile time. GIL's dynamic_image extension allows for working with GIL constructs that are specified at run time, also called variants. GIL provides models of a run-time instantiated image, any_image, and a run-time instantiated image view, any_image_view. The mechanisms are in place to create other variants, such as any_pixel, any_pixel_iterator, etc. Most of GIL's algorithms and all of the view transformation functions also work with run-time instantiated image views and binary algorithms, such as copy_pixels can have either or both arguments be variants.
到现在为止,我们已经创建一个泛型函数计算模板图片视图的图片梯度。然而有时,图片视图的属性,诸如它的颜色空间和通道深度,可能在编译期不可用。GIL的dynamic_image扩展允许处理运行期特定的GIL构造类型,或称为variants(注:变数类型)。GIL提供运行期实例化图片any_image和运行期实例化图片视图any_image_view的模型。这里还有其它建立变数类型的机制,诸如any_pixel、any_pixel_iterator等等。大多数GIL算法和所有视图函数都可以工作在运行期实例化图片视图,而二元算法,诸如copy_pixels可以把变数类型作为一个或全部参数。
Lets make our x_luminosity_gradient algorithm take a variant image view. For simplicity, let's assume that only the source view can be a variant. (As an example of using multiple variants, see GIL's image view algorithm overloads taking multiple variants.)
让我们制造我们自己的可传入变数类型的x_luminosity_gradient算法。简单起见,让我们假设只有源视图是变数的。(作为使用多变数的示例,参考GIL图片视图算法的带多个变数的重载)
First, we need to make a function object that contains the templated destination view and has an application operator taking a templated source view:
首先,我们需要制造一个函数对象(注:即functor,仿函数),包含模板化目标视图和一个带模板化源视图参数的应用操作符(注:即operator()):
#include <boost/gil/extension/dynamic_image/dynamic_image_all.hpp>
template <typename DstView>
struct x_gradient_obj {
typedef void result_type; // required typedef //需要typedef
const DstView& _dst;
x_gradient_obj(const DstView& dst) : _dst(dst) {}
template <typename SrcView>
void operator()(const SrcView& src) const { x_luminosity_gradient(src, _dst); }
};
The second step is to provide an overload of x_luminosity_gradient that takes image view variant and calls GIL's apply_operation passing it the function object:
第二步是提供一个x_luminosity_gradient的重载,以图片视图变数为参数,调用GIL的apply_operation向它传递函数对象:
template <typename SrcViews, typename DstView>
void x_luminosity_gradient(const any_image_view<SrcViews>& src, const DstView& dst) {
apply_operation(src, x_gradient_obj<DstView>(dst));
}
any_image_view<SrcViews> is the image view variant. It is templated over SrcViews, an enumeration of all possible view types the variant can take. src contains inside an index of the currently instantiated type, as well as a block of memory containing the instance. apply_operation goes through a switch statement over the index, each case of which casts the memory to the correct view type and invokes the function object with it. Invoking an algorithm on a variant has the overhead of one switch statement. Algorithms that perform an operation for each pixel in an image view have practically no performance degradation when used with a variant.
any_image_view<SrcViews>是一个图片视图变数。它的模板参数SrcViews是变数可以携带的所有可能的视图类型的一个枚举。src之中包含一个当前实例化类型,还有一个承载实例的内存块。apply_operation流经一个针对索引的switch语句,它的每个case把内存转换成正确的视图类型并且用它调用函数对象。在变数上调用一个算法有一个switch语句的开销。当使用一个变数时,对图片视图的每个像素执行一个操作的算法实际上不会有性能下降。
Here is how we can construct a variant and invoke the algorithm:
这里展示我们如何构造一个变数并且调用算法:
#include <boost/mpl/vector.hpp>
#include <boost/gil/extension/io/jpeg_dynamic_io.hpp>
typedef mpl::vector<gray8_image_t, gray16_image_t, rgb8_image_t, rgb16_image_t> my_img_types;
any_image<my_img_types> runtime_image;
jpeg_read_image("input.jpg", runtime_image);
gray8s_image_t gradient(runtime_image.dimensions());
x_luminosity_gradient(const_view(runtime_image), view(gradient));
jpeg_write_view("x_gradient.jpg", color_converted_view<gray8_pixel_t>(const_view(gradient)));
In this example, we create an image variant that could be 8-bit or 16-bit RGB or grayscale image. We then use GIL's I/O extension to load the image from file in its native color space and channel depth. If none of the allowed image types matches the image on disk, an exception will be thrown. We then construct a 8 bit signed (i.e. char) image to store the gradient and invoke x_gradient on it. Finally we save the result into another file. We save the view converted to 8-bit unsigned, because JPEG I/O does not support signed char.
在这个例子中,我们创建一个图片变数,它可能是8位或16位RGB或灰度图。然后我们使用GIL的I/O扩展从文件中以原生的颜色空间和通道深度加载图片。如果硬盘的图片没有允许的图片类型可匹配,将抛出一个异常。然后我们构造一个8位有符号(即char)图片以保存梯度并在它上面调用x_gradient。最后我们把结果保存进另一个文件。我们保存转为8位无符号的视图因为JPEG I/O不支持有符号char。
Note how free functions and methods such as jpeg_read_image, dimensions, view and const_view work on both templated and variant types. For templated images view(img) returns a templated view, whereas for image variants it returns a view variant. For example, the return type of view(runtime_image) is any_image_view<Views> where Views enumerates four views corresponding to the four image types. const_view(runtime_image) returns a any_image_view of the four read-only view types, etc.
注意自由函数和方法诸如jpeg_read_image,dimensions,view和const_view是如何工作在模板和变数类型上的。
对于模板化图片view(img)返回一个模板化的view,而对于图片变数则返回视图变数。
例如,view(runtime_image)的返回类型是any_image_view<Views>,其中Views枚举了对应4种图片类型的四种视图。 const_view(runtime_image)返回4种只读视图的any_image_view,等等。
A warning about using variants: instantiating an algorithm with a variant effectively instantiates it with every possible type the variant can take. For binary algorithms, the algorithm is instantiated with every possible combination of the two input types! This can take a toll on both the compile time and the executable size.
关于使用变数的警告:用一个变数实例化一个算法会有效地用变数能携带的每个可能的类型来实例化它。对于二元算法,算法用两个输入类型的每个可能组合来实例化!这可能花费编译时间和增大可执行文件的大小。
Conclusion
结论
This tutorial provides a glimpse at the challenges associated with writing generic and efficient image processing algorithms in GIL. We have taken a simple algorithm and shown how to make it work with image representations that vary in bit depth, color space, ordering of the channels, and planar/interleaved structure. We have demonstrated that the algorithm can work with fully abstracted virtual images, and even images whose type is specified at run time. The associated video presentation also demonstrates that even for complex scenarios the generated assembly is comparable to that of a C version of the algorithm, hand-written for the specific image types.
这个教程提供关于使用GIL书写泛型和高效图片处理算法的难题的一瞥。我们已经得到一个简单的算法并展示如何使它处理不同位深、颜色空间、通道顺序、以及平面/间隔结构的图片表现。我们已经演示了可以工作于抽象虚拟图片,甚至运行期特定类型图片的算法。相关视频也会展示一些的演示,针对复杂场景生成的组合,可以与C版本和对特定图片类型而手写的算法媲美。
Yet, even for such a simple algorithm, we are far from making a fully generic and optimized code. In particular, the presented algorithms work on homogeneous images, i.e. images whose pixels have channels that are all of the same type. There are examples of images, such as a packed 565 RGB format, which contain channels of different types. While GIL provides concepts and algorithms operating on heterogeneous pixels, we leave the task of extending x_gradient as an exercise for the reader. Second, after computing the value of the gradient we are simply casting it to the destination channel type. This may not always be the desired operation. For example, if the source channel is a float with range [0..1] and the destination is unsigned char, casting the half-difference to unsigned char will result in either 0 or 1. Instead, what we might want to do is scale the result into the range of the destination channel. GIL's channel-level algorithms might be useful in such cases. For example, channel_convert converts between channels by linearly scaling the source channel value into the range of the destination channel.
但是,即使只是简单的算法,我们还无法写出完全泛型和优化的代码。特别地,给出的算法只能工作在同质图片,即像素拥有同类型通道的图片。这种图片,例如一个打包的565 RGB格式,包含不同类型的通道。然而GIL提供概念和算法操作混杂像素,我们把扩展x_gradient的任务作为练习留给读者。其次,在计算梯度值之后,我们简单地把它转换成目标通道类型,这可能不总是理想的操作。例如,如果源通道时一个区间为[0..1]的浮点数,而目标是一个unsigned char,把半差分转换为unsigned char的结果将是0或1。取而代之,我们可能想做的是把结果缩放进目标通道的区间。GIL的通道级算法可能在这些情况下有用。例如,channel_convert在通道间转换,把源通道值线性缩放到目标通道中。
There is a lot to be done in improving the performance as well. Channel-level operations, such as the half-difference, could be abstracted out into atomic channel-level algorithms and performance overloads could be provided for concrete channel types. Processor-specific operations could be used, for example, to perform the operation over an entire row of pixels simultaneously, or the data could be prefetched. All of these optimizations can be realized as performance specializations of the generic algorithm. Finally, compilers, while getting better over time, are still failing to fully optimize generic code in some cases, such as failing to inline some functions or put some variables into registers. If performance is an issue, it might be worth trying your code with different compilers.
对于提升性能来说还有很多东西要做。通道级操作,诸如半差分,可以抽象出来放入原子通道级算法,而对实际通道类型可能有性能开销。可以使用处理器特定的操作,例如对一整行像素同时执行操作,或者数据能预取。所有这些优化可以实现为泛型算法的性能特化(注:这里可能指模板特化)。最后,编译器,虽然在时间上较好,但在某些情况下仍然无法对生成完全优化的泛型代码,诸如无法行内展开一些函数或把一些变量放入寄存器。如果性能是一个问题,你的代码可能值得用不同的编译器测试。
Appendix
附录
Naming convention for GIL concrete types
GIL实际类型的命名约定
Concrete (non-generic) GIL types follow this naming convention:
实际(非泛型)GIL类型遵循以下命名约定:(注:这里+表示字符串连接)
ColorSpace + BitDepth + [f | s]+ [c] + [_planar] + [_step] + ClassType + _t
色空间(注:原色的排列) + 位深(注:通道的位数) + [f | s] + [c(注:常量性,不可变)] + [_planar(注:平面)] + 类名称(注:图片、像素、引用、指针等) + _t
Where ColorSpace also indicates the ordering of components. Examples are rgb, bgr, cmyk, rgba. BitDepth indicates the bit depth of the color channel. Examples are 8,16,32. By default the type of channel is unsigned integral; using s indicates signed integral and f - a floating point type, which is always signed. c indicates object operating over immutable pixels. _planar indicates planar organization (as opposed to interleaved). _step indicates special image views, locators and iterators which traverse the data in non-trivial way (for example, backwards or every other pixel). ClassType is _image (image), _view (image view), _loc (pixel 2D locator) _ptr (pixel iterator), _ref (pixel reference), _pixel (pixel value).
其中ColorSpace还表明构成的次序。例如rgb,bgr,cmyk,rgba。BitDepth表明颜色通道的位数,例如8,16,32。默认通道类型是无符号整型;使用s表明是有符号整型而f表明是总是有符号的浮点数。 c表明操作只读像素的对象。 _planar表明是平面结构的(不同于交错)(注:“平面”指同一通道的像素在内存上是连续的)。 _step表明以非细微的方式(例如向后遍历或者每隔一个像素的遍历)遍历数据的特殊的图片视图、定位器和迭代器。 ClassType是_image(图片)、_view(图片视图)、_loc(像素二维定位器)、_ptr(像素迭代器)、_ref(像素引用), _pixel(像素值).
bgr8_image_t a; // 8-bit interleaved BGR image // 8位交错BGR图片
cmyk16_pixel_t; b; // 16-bit CMYK pixel value; // 16位CMYK像素值;
cmyk16c_planar_ref_t c(b); // const reference to a 16-bit planar CMYK pixel x. // 指向16位平面的CMYK像素x的常量引用。
rgb32f_planar_step_ptr_t d; // step pointer to a 32-bit planar RGB pixel. // 指向32位平面RGB像素的步进指针。
Copyright (c) 2005 Adobe Systems Incorporated
(译后注:一篇非常棒的教程,对C++代码优化的见解非常精辟,很值得反复斟酌和回味)
更新日志:
2012-07-04:从回收站中粘贴出来,重新发布,内容无改变。
2011-01-14:翻译并发布(后删除)