编写高效的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)

相关文章
|
2月前
|
缓存 C# Windows
C#程序如何编译成Native代码
【10月更文挑战第15天】在C#中,可以通过.NET Native和第三方工具(如Ngen.exe)将程序编译成Native代码,以提升性能和启动速度。.NET Native适用于UWP应用,而Ngen.exe则通过预编译托管程序集为本地机器代码来加速启动。不过,这些方法也可能增加编译时间和部署复杂度。
116 2
|
3月前
|
数据采集 JavaScript C#
C#图像爬虫实战:从Walmart网站下载图片
C#图像爬虫实战:从Walmart网站下载图片
|
2月前
|
C# 数据安全/隐私保护 计算机视觉
C#开发者的新选择:使用ImageSharp进行图像处理
C#开发者的新选择:使用ImageSharp进行图像处理
148 8
|
2月前
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
135 0
|
2月前
|
XML 存储 安全
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
69 0
|
2月前
|
安全 API C#
C# 如何让程序后台进程不被Windows任务管理器强制结束
C# 如何让程序后台进程不被Windows任务管理器强制结束
66 0
|
3月前
|
C# 容器
C#中的命名空间与程序集管理
在C#编程中,`命名空间`和`程序集`是组织代码的关键概念,有助于提高代码的可维护性和复用性。本文从基础入手,详细解释了命名空间的逻辑组织方式及其基本语法,展示了如何使用`using`指令访问其他命名空间中的类型,并提供了常见问题的解决方案。接着介绍了程序集这一.NET框架的基本单位,包括其创建、引用及高级特性如强名称和延迟加载等。通过具体示例,展示了如何创建和使用自定义程序集,并提出了针对版本不匹配和性能问题的有效策略。理解并善用这些概念,能显著提升开发效率和代码质量。
111 4
|
2月前
|
API C#
C#实现Winform程序右下角弹窗消息提示
C#实现Winform程序右下角弹窗消息提示
95 0
|
3月前
|
Linux C# 开发者
Uno Platform 驱动的跨平台应用开发:从零开始的全方位资源指南与定制化学习路径规划,助您轻松上手并精通 C# 与 XAML 编程技巧,打造高效多端一致用户体验的移动与桌面应用程序
【9月更文挑战第8天】Uno Platform 的社区资源与学习路径推荐旨在为初学者和开发者提供全面指南,涵盖官方文档、GitHub 仓库及社区支持,助您掌握使用 C# 和 XAML 创建跨平台原生 UI 的技能。从官网入门教程到进阶技巧,再到活跃社区如 Discord,本指南带领您逐步深入了解 Uno Platform,并提供实用示例代码,帮助您在 Windows、iOS、Android、macOS、Linux 和 WebAssembly 等平台上高效开发。建议先熟悉 C# 和 XAML 基础,然后实践官方教程,研究 GitHub 示例项目,并积极参与社区讨论,不断提升技能。
99 2