本文共 10874 字,大约阅读时间需要 36 分钟。
最近项目需要检测图像是否存在、过亮、模糊等缺陷。由于主要用在视频监控上,对性能要求比较高。有几项检测必须要在Lab彩色下进行,而众所周知Rgb => Lab 计算量较大,C#搞得定搞不定?测试表明,用纯C#编写的Rgb => Lab代码在性能上与C编写的Rgb => Lab代码极为接近。
Rgb是电脑上使用较多的彩色空间,Lab是针对人的感知设计的均匀彩色空间,很多情况下进行彩色图像分析,需要在Rgb彩色空间和Lab彩色空间之间进行转化。关于Lab彩色空间的详细介绍和Rgb空间与Lab空间的转换公式见维基百科的对应词条 ,本文不再叙述。
使用Rgb24和Lab24两个struct定义Rgb彩色空间的像素和Lab彩色空间的像素。
Lab空间参照OpenCV,用一个byte来表示Lab空间的每个通道值,以求提高性能。由于标准的Lab空间中a和b通道是可付的,Lab24中的A、B值减去128,就是标准Lab空间的a,b通道值。
OpenCV中Bgr<=>Lab是用C语言实现的,下面将它转换为C#代码:
由于C代码中使用了宏,在改写成C#代码时需要手动内联,以提高性能。上面的代码已经实现手动内联。
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的性能极为接近。
偏色、高光检测等不需要多么准确的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
还可以进一步提高性能,因为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
经常有人问,你为什么用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 实现与 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
====
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下使用无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)