编写高效的C#图像处理程序(3) Rgb=>Lab,图像缺陷检测的例子

简介:

最近项目需要检测图像是否存在偏色、过亮、模糊等缺陷。由于主要用在视频监控上,对性能要求比较高。有几项检测必须要在Lab彩色下进行,而众所周知Rgb => Lab 计算量较大,C#搞得定搞不定?测试表明,用纯C#编写的Rgb => Lab代码在性能上与C编写的Rgb => Lab代码极为接近。

1. Rgb24和Lab24

Rgb是电脑上使用较多的彩色空间,Lab是针对人的感知设计的均匀彩色空间,很多情况下进行彩色图像分析,需要在Rgb彩色空间和Lab彩色空间之间进行转化。关于Lab彩色空间的详细介绍和Rgb空间与Lab空间的转换公式见维基百科的对应词条 Lab色彩空间,本文不再叙述。

使用Rgb24和Lab24两个struct定义Rgb彩色空间的像素和Lab彩色空间的像素。

Rgb24 与 Lab24

Lab空间参照OpenCV,用一个byte来表示Lab空间的每个通道值,以求提高性能。由于标准的Lab空间中a和b通道是可付的,Lab24中的A、B值减去128,就是标准Lab空间的a,b通道值。

2. Rgb24 <=> Lab24 的实现

OpenCV中Bgr<=>Lab是用C语言实现的,下面将它转换为C#代码:

Rgb24 <=> Lab24

 

由于C代码中使用了宏,在改写成C#代码时需要手动内联,以提高性能。上面的代码已经实现手动内联。

3. (A)C#实现与(B)C实现的性能对比(C# vs. OpenCV/PInvoke)

C# 版本(ImageRgb24 代表一幅Rgb24图像,ImageLab24代表一幅Lab24图像,它们之间的变化是调用上文UnmanagedImageConverter中的方法实现的):

Stopwatch sw = new Stopwatch(); 
sw.Start(); 
ImageLab24 imgLab = null; 
imgLab = new ImageLab24(img);  // img 是一个 ImageRgb24 对象 
sw.Stop(); 
Message = sw.ElapsedMilliseconds.ToString();

OpenCV版本(使用EmguCV对OpenCV的PInvoke封装)

private Image<Lab,Byte> TestOpenCV() 

    Image<Bgr, Byte> imgBgr = new Image<Bgr, byte>(imgMain.Image as Bitmap); 
    Image<Lab,Byte> imgLab = new Image<Lab,byte>(new Size(imgBgr.Width, imgBgr.Height)); 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    CvInvoke.cvCvtColor(imgBgr.Ptr,imgLab.Ptr, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_BGR2Lab); 
    sw.Stop(); 
    MessageBox.Show(sw.ElapsedMilliseconds.ToString() + "ms"); 
    return imgLab; 
}

下面针对三副不同大小的图像进行测试,每张图像测试4次,每次测试将上面两种实现各跑一次,前2次,先跑OpenCV/PInvoke实现,后2次,先跑C#实现,单位皆为ms。

图像1,大小:485×342

A: 5    3    5   3 
B: 41   5    6   2

图像2,大小:1845×611

A:25  23    23   23   
B:23  34    20   21  

图像3,大小:3888×2592

A:209  210  211  210 
B:185  188  191  185

从测试结果可以看出,C# 和 OpenCV/PInvoke的性能极为接近。

4. 进一步改进性能

偏色、高光检测等不需要多么准确的Rgb=>Lab转换。如果把彩色图像的每个通道用4 bit来表示,则一共有 4096 种颜色,完全可以用查表方式来加速计算。用一个Lab24数组来表示Rgb24到Lab24空间的映射:

Lab24[] ColorMap

首先初始化ColorMap:

ColorMap = new Lab24[4096]; 
for (int r = 0; r < 16; r++) 

    for (int g = 0; g < 16; g++) 
    { 
        for (int b = 0; b < 16; b++) 
        { 
            Rgb24 rgb = new Rgb24(r * 16, g * 16, b * 16); 
            Lab24 lab = Lab24.CreateFrom(rgb); 
            ColorMap[(r << 8) + (g << 4) + b] = lab; 
        } 
    } 
}

然后,查表进行转换:

private unsafe ImageLab24 ConvertToImageLab24(ImageRgb24 img) 

    ImageLab24 lab = new ImageLab24(img.Width, img.Height); 
    Lab24* labStart = lab.Start; 
    Rgb24* rgbStart = img.Start; 
    Rgb24* rgbEnd = img.Start + img.Length; 
    while (rgbStart != rgbEnd) 
    { 
        Rgb24 rgb = *rgbStart; 
        *labStart = ColorMap[(((int)(rgb.Red) >> 4) << 8) + (((int)(rgb.Green) >> 4) << 4) + ((int)(rgb.Blue) >> 4) ]; 
        rgbStart++; 
        labStart++; 
    } 
    return lab; 
}

下面测试(C)查表计算的性能,结果和(A)C#实现与(B)C实现放在一起做对比。

图像1,大小:485×342

A: 5    3    5   3 
B: 41   5    6   2 
C: 3    2    2    2

图像2,大小:1845×611

A:25  23    23   23   
B:23  34    20   21   
C:  15   15   15   15

图像3,大小:3888×2592

A:209  210  211  210 
B:185  188  191  185 
C:  136  134  135  135

5. 原地进行变换

还可以进一步提高性能,因为Rgb24和Lab24大小一样,可以在原地进行Rgb24=>Lab24的变换。相应代码如下:

Rgb24[] ColorMapInSpace 
...            
ColorMap = new Lab24[4096]; 
ColorMapInSpace = new Rgb24[4096]; 
for (int r = 0; r < 16; r++) 

    for (int g = 0; g < 16; g++) 
    { 
        for (int b = 0; b < 16; b++) 
        { 
            Rgb24 rgb = new Rgb24(r * 16, g * 16, b * 16); 
            Lab24 lab = Lab24.CreateFrom(rgb); 
            ColorMap[(r << 8) + (g << 4) + b] = lab; 
            ColorMapInSpace[(r << 8) + (g << 4) + b] = new Rgb24(lab.L,lab.A,lab.B); 
        } 
    } 
}

private unsafe void ConvertToImageLab24InSpace(ImageRgb24 img) 

    Rgb24* rgbStart = img.Start; 
    Rgb24* rgbEnd = img.Start + img.Length; 
    while (rgbStart != rgbEnd) 
    { 
        Rgb24 rgb = *rgbStart; 
        *rgbStart = ColorMapInSpace[(((int)(rgb.Red) >> 4) << 8) + (((int)(rgb.Green) >> 4) << 4) + ((int)(rgb.Blue) >> 4)]; 
        rgbStart++; 
    } 
}

下面测试D(原地查表变换)的性能,结果和(A)C#实现、(B)C实现、(C)查表计算进行比较:

图像1,大小:485×342

A: 5    3    5   3 
B: 41   5    6   2 
C: 3    2    2    2 
D: 2    1    2    1

图像2,大小:1845×611

A:25  23    23   23   
B:23  34    20   21   
C:  15   15   15   15  
D:  13   13   13   13

图像3,大小:3888×2592

A:209  210  211  210 
B:185  188  191  185 
C:  136  134  135  135 
D:  117  118  122  117

6. 为什么用C#而不是C/C++

经常有人问,你为什么用C#而不用C/C++写图像处理程序。原因如下:

(1)C# 打开unsafe后,写的程序性能非常接近 C 程序的性能(当然,用不了SIMD是个缺陷。mono暂时不考虑。可通过挂接一个轻量级的C库来解决。);

(2)写C#代码比写C代码爽多了快多了(命名空间、不用管头文件、快速编译、重构、生成API文档 ……);

(3)庞大的.Net Framework是强有力的后盾。比如,客户想看演示,用Asp.Net写个页面,传个图片给后台,处理了显示出来。还有那些非性能攸关的地方,可以大量使用.Net Framework中的类,大幅度减少开发时间;

(4)结合强大的WPF,可以快速实现复杂的功能

(5)大量的时间在算法研究、实现和优化上,用C#可以把那些无关的惹人烦的事情给降到最小,所牺牲的只是一丁点儿性能。如果生产平台没有.net环境,将C#代码转换为C/C++代码也很快。

====

补充测试VC 9.0 版本

VC 实现与 C# 实现略有区别,C#版本RGB,Lab使用struct来表示,VC下直接用的三个Byte Channel来表示,然后以 redChannel, greenChannel, blueChannel 来代表不同的 Channel Offset。以 nChannel 代表 Channel 数量。VC下有Stride,C#下无Stride。查表实现也和C#版本有区别,直接使用的是静态的表。O2优化。

E: 非查表实现

void 
::ImageQualityDetector::ConvertToLab(Orc::ImageInfo &img) 

    static unsigned short icvLabCubeRootTab[] = { 
        0,161,203……        };

    const float labXr_32f = 0.433953f /* = xyzXr_32f / 0.950456 */; 
    const float labXg_32f = 0.376219f /* = xyzXg_32f / 0.950456 */; 
    const float labXb_32f = 0.189828f /* = xyzXb_32f / 0.950456 */;

    const float labYr_32f = 0.212671f /* = xyzYr_32f */; 
    const float labYg_32f = 0.715160f /* = xyzYg_32f */; 
    const float labYb_32f = 0.072169f /* = xyzYb_32f */;

    const float labZr_32f = 0.017758f /* = xyzZr_32f / 1.088754 */; 
    const float labZg_32f = 0.109477f /* = xyzZg_32f / 1.088754 */; 
    const float labZb_32f = 0.872766f /* = xyzZb_32f / 1.088754 */;

    const float labRx_32f = 3.0799327f  /* = xyzRx_32f * 0.950456 */; 
    const float labRy_32f = (-1.53715f) /* = xyzRy_32f */; 
    const float labRz_32f = (-0.542782f)/* = xyzRz_32f * 1.088754 */;

    const float labGx_32f = (-0.921235f)/* = xyzGx_32f * 0.950456 */; 
    const float labGy_32f = 1.875991f   /* = xyzGy_32f */ ; 
    const float labGz_32f = 0.04524426f /* = xyzGz_32f * 1.088754 */;

    const float labBx_32f = 0.0528909755f /* = xyzBx_32f * 0.950456 */; 
    const float labBy_32f = (-0.204043f)  /* = xyzBy_32f */; 
    const float labBz_32f = 1.15115158f   /* = xyzBz_32f * 1.088754 */;

    const float labT_32f = 0.008856f;

    const int lab_shift = 10;

    const float labLScale2_32f = 903.3f;

    const int labXr = (int)((labXr_32f) * (1 << (lab_shift)) + 0.5); 
    const int labXg = (int)((labXg_32f) * (1 << (lab_shift)) + 0.5); 
    const int labXb = (int)((labXb_32f) * (1 << (lab_shift)) + 0.5);

    const int labYr = (int)((labYr_32f) * (1 << (lab_shift)) + 0.5); 
    const int labYg = (int)((labYg_32f) * (1 << (lab_shift)) + 0.5); 
    const int labYb = (int)((labYb_32f) * (1 << (lab_shift)) + 0.5);

    const int labZr = (int)((labZr_32f) * (1 << (lab_shift)) + 0.5); 
    const int labZg = (int)((labZg_32f) * (1 << (lab_shift)) + 0.5); 
    const int labZb = (int)((labZb_32f) * (1 << (lab_shift)) + 0.5);

    const float labLScale_32f = 116.0f; 
    const float labLShift_32f = 16.0f;

    const int labSmallScale = (int)((31.27 /* labSmallScale_32f*(1<<lab_shift)/255 */ ) * (1 << (lab_shift)) + 0.5);

    const int labSmallShift = (int)((141.24138 /* labSmallScale_32f*(1<<lab) */ ) * (1 << (lab_shift)) + 0.5);

    const int labT = (int)((labT_32f * 255) * (1 << (lab_shift)) + 0.5);

    const int labLScale = (int)((295.8) * (1 << (lab_shift)) + 0.5); 
    const int labLShift = (int)((41779.2) * (1 << (lab_shift)) + 0.5); 
    const int labLScale2 = (int)((labLScale2_32f * 0.01) * (1 << (lab_shift)) + 0.5);

    int width = img.Width; 
    int height = img.Height; 
    int nChannel = img.NChannel; 
    int redChannel = img.RedChannel; 
    int greenChannel = img.GreenChannel; 
    int blueChannel = img.BlueChannel; 
    int x, y, z; 
    int l, a, b; 
    bool flag;

    for(int h = 0; h < height; h++) 
    { 
        byte *line = img.GetLine(h); 
        for(int w = 0; w < width; w++) 
        { 
            int red = line[redChannel]; 
            int green = line[greenChannel]; 
            int blue = line[blueChannel];

            x = blue * labXb + green * labXg + red * labXr; 
            y = blue * labYb + green * labYg + red * labYr; 
            z = blue * labZb + green * labZg + red * labZr;

            flag = x > labT;

            x = (((x) + (1 << ((lab_shift) - 1))) >> (lab_shift));

            if (flag) 
                x = icvLabCubeRootTab[x]; 
            else 
                x = (((x * labSmallScale + labSmallShift) + (1 << ((lab_shift) - 1))) >> (lab_shift));

            flag = z > labT; 
            z = (((z) + (1 << ((lab_shift) - 1))) >> (lab_shift));

            if (flag == true) 
                z = icvLabCubeRootTab[z]; 
            else 
                z = (((z * labSmallScale + labSmallShift) + (1 << ((lab_shift) - 1))) >> (lab_shift));

            flag = y > labT; 
            y = (((y) + (1 << ((lab_shift) - 1))) >> (lab_shift));

            if (flag == true) 
            { 
                y = icvLabCubeRootTab[y]; 
                l = (((y * labLScale - labLShift) + (1 << ((2 * lab_shift) - 1))) >> (2 * lab_shift)); 
            } 
            else 
            { 
                l = (((y * labLScale2) + (1 << ((lab_shift) - 1))) >> (lab_shift)); 
                y = (((y * labSmallScale + labSmallShift) + (1 << ((lab_shift) - 1))) >> (lab_shift)); 
            }

            a = (((500 * (x - y)) + (1 << ((lab_shift) - 1))) >> (lab_shift)) + 129; 
            b = (((200 * (y - z)) + (1 << ((lab_shift) - 1))) >> (lab_shift)) + 128;

            l = l > 255 ? 255 : l < 0 ? 0 : l; 
            a = a > 255 ? 255 : a < 0 ? 0 : a; 
            b = b > 255 ? 255 : b < 0 ? 0 : b;

            int index = 3 * (((red >> 4) << 8) + ((green >> 4) << 4) + (blue >> 4)) ; 
            line[0] = (byte)l; 
            line[1] = (byte)a; 
            line[2] = (byte)b;

            line += nChannel; 
        } 
    } 
}

F: 查表实现

void 
::ImageQualityDetector::FastConvertToLab(Orc::ImageInfo &img) 

    static const byte Rgb2LabSmallTable[] = { 
    0,    129,    128 …… 
    };

    int width = img.Width; 
    int height = img.Height; 
    int nChannel = img.NChannel; 
    int redChannel = img.RedChannel; 
    int greenChannel = img.GreenChannel; 
    int blueChannel = img.BlueChannel; 
    for(int h = 0; h < height; h++) 
    { 
        byte *line = img.GetLine(h); 
        for(int w = 0; w < width; w++) 
        { 
            int red = line[redChannel]; 
            int green = line[greenChannel]; 
            int blue = line[blueChannel]; 
            int index = 3 * (((red >> 4) << 8) + ((green >> 4) << 4) + (blue >> 4)) ; 
            line[0] = Rgb2LabSmallTable[index]; 
            line[1] = Rgb2LabSmallTable[index + 1]; 
            line[2] = Rgb2LabSmallTable[index + 2]; 
            line += nChannel; 
        } 
    } 
}

测试结果:

图像2,大小:1845×611

A:25  23    23   23   
B:23  34    20   21   
C:  15   15   15   15  
D:  13   13   13   13 
E:  32   30   37   37 
F:  15    10   13  11

图像3,大小:3888×2592

A:209  210  211  210 
B:185  188  191  185 
C:  136  134  135  135 
D:  117  118  122  117 
E:  242  240  243  239 
F:  70    69    67    67

====

补充测试:C# 下查表实现(Byte数组)

G: C#下直接查找Byte数组,相关代码

static byte[] Rgb2LabSmallTable = new byte[] { 
    0,    129,    128, … }

private unsafe void ConvertToImageLab24Fast(ImageRgb24 img) 

    Rgb24* rgbStart = img.Start; 
    Rgb24* rgbEnd = img.Start + img.Length; 
    while (rgbStart != rgbEnd) 
    { 
        Rgb24 rgb = *rgbStart; 
        int index = (((int)(rgb.Red) >> 4) << 8) + (((int)(rgb.Green) >> 4) << 4) + ((int)(rgb.Blue) >> 4); 
        rgbStart->Red = Rgb2LabSmallTable[index]; 
        rgbStart->Green = Rgb2LabSmallTable[index+1]; 
        rgbStart->Blue = Rgb2LabSmallTable[index+2]; 
        rgbStart++; 
    } 
}

测试结果:

图像2,大小:1845×611

A:25  23    23   23   
B:23  34    20   21   
C:  15   15   15   15  
D:  13   13   13   13 
E:  32   30   37   37 
F:  15    10   13  11 
G:  12    11   13  11

图像3,大小:3888×2592

A:209  210  211  210 
B:185  188  191  185 
C:  136  134  135  135 
D:  117  118  122  117 
E:  242  240  243  239 
F:  70    69    67    67 
G:  64    64    65    64

====

补充测试:同一种实现下的C#和VC性能对比,附下载

下面消除两种语言的测试区别,C#版本查表时使用指针而非数组,VC下使用无Stride的Rgb24,相关测试代码见 下载链接 。

这又形成了4个测试用例:

H- C#,非查表;I-C#,查表; J-C++,非查表; K-C++,查表

C# 版为 .Net 4.0, VS2010 ,代码中选择快速一项为测试I,不选择为测试H。

C++版 - VS2008。选择快速一项为测试K,不选择为测试J。

测试结果:

图像2,大小:1845×611

H: 31  29  36  32 
I:  10  10  10  10 
J:  39  33  33  30 
K:  9    8    8    8

图像3,大小:3888×2592

H: 195  194  194  195 
I:  53    52    51    52 
J: 220  218  218  222 
K: 41   42    41   41

结论:

C#下图像开发是很给力的!还在犹豫什么呢?

本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2011/01/13/1934170.html如需转载请自行联系原作者

 

xiaotie 集异璧实验室(GEBLAB)

相关文章
|
4月前
|
C# 开发者
C# 9.0中的模块初始化器:程序启动的新控制点
【1月更文挑战第14天】本文介绍了C# 9.0中引入的新特性——模块初始化器(Module initializers)。模块初始化器允许开发者在程序集加载时执行特定代码,为类型初始化提供了更细粒度的控制。文章详细阐述了模块初始化器的语法、用途以及与传统类型初始化器的区别,并通过示例代码展示了如何在实际项目中应用这一新特性。
|
4月前
|
编译器 C# 开发者
C# 9.0中的顶级语句:简化程序入口的新特性
【1月更文挑战第13天】本文介绍了C# 9.0中引入的顶级语句(Top-level statements)特性,该特性允许开发者在不使用传统的类和方法结构的情况下编写简洁的程序入口代码。文章详细阐述了顶级语句的语法、使用场景以及与传统程序结构的区别,并通过示例代码展示了其在实际应用中的便捷性。
|
10天前
|
前端开发 Java C#
C#程序启动后乱码(通过idea启动)
C#程序启动后乱码(通过idea启动)
14 0
|
27天前
|
JavaScript C#
C#winForm程序与html JS交互调用
C#winForm程序与html JS交互调用
|
2月前
|
Java C# 开发工具
第一个C#程序
第一个C#程序
12 0
|
2月前
|
数据采集 存储 C#
抓取Instagram数据:Fizzler库带您进入C#程序的世界
在当今数字化的世界中,数据是无价之宝。社交媒体平台如Instagram成为了用户分享照片、视频和故事的热门场所。作为开发人员,我们可以利用爬虫技术来抓取这些平台上的数据,进行分析、挖掘和应用。本文将介绍如何使用C#编写一个简单的Instagram爬虫程序,使用Fizzler库来解析HTML页面,同时利用代理IP技术提高采集效率。
抓取Instagram数据:Fizzler库带您进入C#程序的世界
|
5月前
|
C#
C# 三层级架构问题之 能加载文件或程序集或它的某一个依赖项。系统找不到指定的文件
C# 三层级架构问题之 能加载文件或程序集或它的某一个依赖项。系统找不到指定的文件
31 0
|
5月前
|
编译器 应用服务中间件 数据库连接
解决C#运行程序修改数据后数据表不做更新的问题
解决C#运行程序修改数据后数据表不做更新的问题
44 0
|
6月前
|
数据可视化 BI C#
C#程序采用AOT发布,真的可以避免被反编译?
C#程序采用AOT发布,真的可以避免被反编译?
114 0
|
6月前
|
监控 关系型数据库 MySQL
C#程序发布时,一定要好好地保护,不然你会后悔的
C#程序发布时,一定要好好地保护,不然你会后悔的
37 0