博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编写高效的C#图像处理程序(3) Rgb=>Lab,图像缺陷检测的例子
阅读量:6476 次
发布时间:2019-06-23

本文共 10874 字,大约阅读时间需要 36 分钟。

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

1. Rgb24和Lab24

Rgb是电脑上使用较多的彩色空间,Lab是针对人的感知设计的均匀彩色空间,很多情况下进行彩色图像分析,需要在Rgb彩色空间和Lab彩色空间之间进行转化。关于Lab彩色空间的详细介绍和Rgb空间与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)

你可能感兴趣的文章
SHOW CREATE DATABASE Syntax
查看>>
rsync常见问题及解决办法
查看>>
MySQL日期 专题
查看>>
C#中禁止程序多开
查看>>
分布式缓存Redis使用以及原理
查看>>
Activity竟然有两个onCreate方法,可别用错了
查看>>
Linux经常使用命令(十六) - whereis
查看>>
插件编译 版本问题
查看>>
android中TextView的阴影设置
查看>>
core dump相关
查看>>
Linux五种IO模型
查看>>
Bootstrap技术: 模式对话框的使用
查看>>
小知识,用myeclipes找jar
查看>>
in-list expansion
查看>>
设计原则(四):接口隔离原则
查看>>
基于react的滑动图片验证码组件
查看>>
iOS快速清除全部的消息推送
查看>>
java单例模式深度解析
查看>>
【学习笔记】阿里云Centos7.4下配置Nginx
查看>>
VuePress手把手一小時快速踩坑
查看>>