我正在为设备编写一个小型的色彩空间转换器,该转换器记录图像并将其显示在屏幕上。该图像可用作平面4:2:2 YUV图像,并且需要转换为8位/通道RGB图像。在不久的将来,输入图像的色彩空间可能会发生变化(可能变为打包的YUV图像),而目标色彩空间也会发生变化(可能变为16位/通道RGB图像)。因此,作为一项练习,我想使我的色彩转换器易于适应。我决定有一个基本的图像类,该类为所有图像类型提供统一的接口,并使用CRTP进行静态多态,因为在编译时就会知道这些图像类型。以下简化代码显示了图像类型的实现:
template <typename ImageType, typename ValueType>
class ImageBase {
public:
using value_type = ValueType;
const std::size_t cols;
const std::size_t rows;
value_type* data;
std::size_t size() const {
return static_cast<const ImageType*>(this)->sizeImpl();
}
template <typename T>
T readAt(std::size_t index) {
return static_cast<const ImageType*>(this)->template readAtImpl<T>(index);
}
virtual ~ImageBase(){
}
protected:
ImageBase(std::size_t cols, std::size_t rows): cols(cols), rows(rows), data(nullptr) {}
ImageBase(std::size_t cols, std::size_t rows, value_type* data): cols(cols), rows(rows), data(data) {}
};
class RGB888: public ImageBase<RGB888, unsigned char> {
friend ImageBase<RGB888, unsigned char>;
private:
std::size_t sizeImpl() const{
// Get size of type RGB888
}
template <typename T>
T readAtImpl(std::size_t index) const {
// Return a RGB struct for pixel(index) from raw data
}
public:
using value_type = unsigned char;
RGB888(std::size_t cols, std::size_t rows): ImageBase(cols, rows) {}
RGB888(std::size_t cols, std::size_t rows, unsigned char* data): ImageBase(cols, rows, data) {}
};
class YUV422_LOGIISP: public ImageBase<YUV422_LOGIISP, unsigned char> {
friend ImageBase<YUV422_LOGIISP, unsigned char>;
private:
std::size_t sizeImpl() const{
// Get size of type YUV422_LOGIISP
}
template <typename T>
T readAtImpl(std::size_t index) const {
// Return a YUV struct for pixel(index) from raw data
}
public:
using value_type = unsigned char;
YUV422_LOGIISP(std::size_t cols, std::size_t rows): ImageBase(cols, rows) {}
YUV422_LOGIISP(std::size_t cols, std::size_t rows, unsigned char* data): ImageBase(cols, rows, data) {}
template <typename T>
RGB_t<T> yuv2rgb(double y, double u, double v) {
// Convert a set of y, u, v to a single RGB pixel
}
};
转换的实现方式如下:
class ColorConverter {
public:
template<typename SRC, typename DST>
struct always_false : std::false_type {};
template <class SRC, class DST>
static void convertImage(SRC const& sourceImage, DST& destinationImage) {
static_assert( always_false<SRC, DST>::value, "No conversion from SRC to DST" );
}
};
template<>
void ColorConverter::convertImage<YUV422_LOGIISP, RGB888>(YUV422_LOGIISP const& src, RGB888& dst) {
// Do actual conversion
// Handle indices to access correct (y,u,v) values
// For every (y, u, v) set, call src.yuc2rgb to obtain a the rgb values
}
这样,在客户端代码中,我可以像这样在YUV和RGB之间转换:
...
// rgbImageData and yuvImageData are allocated somewhere else
RGB888 rgbImage(imageWidth, imageHeight, rgbImageData.data());
YUV422_LOGIISP yuvImage(imageWidth, imageHeight, yuvImageData.data());
ColorConverter::convertImage(yuvImage, rgbImage);
现在,从(y,u,v)集合到(r,g,b)集合的转换非常简单,繁琐的部分是将YUV数据提取到正确的(y,u,v)集合中因为数据是交织的,并且彼此之间并不相邻。我处理ColorConverter::convertImageclass
中的(y,u,v)数据访问,但是实际的转换是从一组(y,u,v)值到(r,g,b)值的调用src.yuv2rgb
。
目前,我有一个专用的转换类,只有 helps 可以转换,但是实际的转换发生在图像类中。我对将yuv2rgb转换函数放置在ColorConverter类中不满意,对将其放置在YUV422_LOGIISP类中也不满意。
如果将函数放入ColorConverter类中,则此类中有关于特殊情况的详细信息。另一方面,如果将函数放在YUV422_LOGIISP类中,则该类还具有转换的责任,并且会破坏统一接口。
这很有可能表明设计有缺陷。没有我的问题...
如何以一种清晰地将关注点分离并最小化依赖关系的方式来构造代码?我应该考虑使用enum
进行转换,类似于在OpenCV中完成转换吗?