1、目的:遍历、修改Mat像素
本文主要是在学习How to scan images, lookup tables and time measurement with OpenCV和OpenCV如何扫描图像、利用查找表和计时过程中,将对应的原文中的C++源码用OpenCVSharp重写,但由于对原文的理解和对OpenCVSharp的了解有限,无法一一重写,只能结合自己的了解,依葫芦画瓢式地进行了重写。
原文章中,主要介绍了一种 颜色空间缩减算法:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。并用多种方式实现该算法,以对比各种方式之间的性能。
本文主要实现用Mat按指针、GetGenericIndexer、GetIndexer、GetUnsafeGenericIndexer、At、Get/Set方式以及LUT方法遍历和修改像素。
上代码前,先看看重复执行100次对应算法的耗时对比吧
Debug模式下:
Release模式下:
2、指针访问Mat
private Mat ScanImageAndReduceByPtr(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
int nRows = I.Rows;
int nCols = I.Cols * channels;
if (I.IsContinuous())
{
nCols *= nRows;
nRows = 1;
}
unsafe
{
for (int i = 0; i < nRows; i++)
{
var p = I.Ptr(i);
byte* b = (byte*)p.ToPointer();
for (int j = 0; j < nCols; j++)
{
b[j] = table[b[j]];
}
}
return I;
}
}
3、GetIndexer
private Mat ScanImageAndReduceByIndexer(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
switch (channels)
{
case 1:
var matByte = new Mat<Byte>(I);
var indexerByte = matByte.GetIndexer();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
indexerByte[r, c] = table[indexerByte[r, c]];
}
}
break;
case 3:
var matVec3b = new Mat<Vec3b>(I);
var indexerVec3b = matVec3b.GetIndexer();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
var vec3b = indexerVec3b[r, c];
indexerVec3b[r, c] = new Vec3b(table[vec3b[0]], table[vec3b[1]], table[vec3b[2]]);
}
}
break;
}
return I;
}
4、GetGenericIndexer
private Mat ScanImageAndReduceByGenericIndexer(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
switch (channels)
{
case 1:
var at = I.GetGenericIndexer<byte>();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
at[r, c] = table[at[r, c]];
}
}
break;
case 3:
var atb = I.GetGenericIndexer<Vec3b>();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
var vec3b = atb[r, c];
atb[r, c] = new Vec3b(table[vec3b[0]], table[vec3b[1]], table[vec3b[2]]);
}
}
break;
}
return I;
}
5、GetUnsafeGenericIndexer
private Mat ScanImageAndReduceByUnsafeIndexer(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
unsafe
{
switch (channels)
{
case 1:
var at = I.GetUnsafeGenericIndexer<byte>();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
at[r, c] = table[at[r, c]];
}
}
break;
case 3:
var atb = I.GetUnsafeGenericIndexer<Vec3b>();
for (int r = 0; r < I.Rows; r++)
{
for (int c = 0; c < I.Cols; c++)
{
var vec3b = atb[r, c];
atb[r, c] = new Vec3b(table[vec3b[0]], table[vec3b[1]], table[vec3b[2]]);
}
}
break;
}
}
return I;
}
6、Mat.At
private Mat ScanImageAndReduceByAt(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
switch (channels)
{
case 1:
for (int i = 0; i < I.Rows; ++i)
{
for (int j = 0; j < I.Cols; ++j)
{
I.At<Byte>(i, j) = table[I.At<Byte>(i, j)];
}
}
break;
case 3:
for (int i = 0; i < I.Rows; ++i)
for (int j = 0; j < I.Cols; ++j)
{
var vec3b = I.At<Vec3b>(i, j);
I.At<Vec3b>(i, j) = new Vec3b(table[vec3b[0]], table[vec3b[1]], table[vec3b[2]]);
}
break;
}
return I;
}
7、Mat.Get和Mat.Set
private Mat ScanImageAndReduceByGetSet(Mat I, byte[] table)
{
Debug.Assert(I.Depth() == MatType.CV_8U);
int channels = I.Channels();
switch (channels)
{
case 1:
for (int i = 0; i < I.Rows; ++i)
{
for (int j = 0; j < I.Cols; ++j)
{
I.Set<Byte>(i, j, table[I.Get<Byte>(i, j)]);
}
}
break;
case 3:
for (int i = 0; i < I.Rows; ++i)
for (int j = 0; j < I.Cols; ++j)
{
var vec3b = I.Get<Vec3b>(i, j);
I.Set<Vec3b>(i, j, new Vec3b(table[vec3b[0]], table[vec3b[1]], table[vec3b[2]]));
}
break;
}
return I;
}
8、LUT
Mat lookUpTable = new Mat(1, 256, MatType.CV_8U);
var p = lookUpTable.Ptr(0);
Mat matByLUT = new Mat();
const int times = 100;
unsafe
{
byte* b = (byte*)p.ToPointer();
for (int i = 0; i < 256; ++i)
b[i] = table[i];
for (int i = 0; i < times; ++i)
{
Cv2.LUT(matClone, lookUpTable, matByLUT);
}
}
9、总结
方式 | Debug模式 | Release模式 |
LUT | 51ms | 43ms |
指针 | 185ms | 65ms |
GetIndexer | 1049ms | 931ms |
GetUnsafeGenericIndexer | 1051ms | 868ms |
At | 1401ms | 1106ms |
GetGenericIndexer | 6581ms | 6551ms |
Get/Set | 7370ms | 6796ms |
由对比可知,LUT方式最优、Get/Set方式最差;GetIndexer与GetUnsafeGenericIndexer基本接近。
图像处理对算法性能的要求较高,如果文中有错误之处,还望您指出,避免误导或踩坑,万分感谢!
因代码使用了unsafe代码,解决方案需启用“允许不安全代码”
补上主函数的代码
public override void Run()
{
//读取图像
using var lena = Cv2.ImRead(ImagePath.LenaColor, ImreadModes.Color);
if (lena.Empty()) return;
int divideWith = 10;
byte[] table = new byte[256];
for (int i = 0; i < 256; ++i)
{
table[i] = (byte)(1.0 * divideWith * (i / divideWith));
}
Mat matByPtr = DoSIAR(ScanImageAndReduceByPtr, lena, table, "ByPtr");
Mat matByGenericIndexer = DoSIAR(ScanImageAndReduceByGenericIndexer, lena, table, "ByGenericIndexer");
Mat matByIndexer = DoSIAR(ScanImageAndReduceByIndexer, lena, table, "ByIndexer");
Mat matByUnsafeIndexer = DoSIAR(ScanImageAndReduceByUnsafeIndexer, lena, table, "ByUnsafeIndexer");
Mat matByAt = DoSIAR(ScanImageAndReduceByAt, lena, table, "ByAt");
Mat matByGetSet = DoSIAR(ScanImageAndReduceByGetSet, lena, table, "ByGetSet"); ;
Mat lookUpTable = new Mat(1, 256, MatType.CV_8U);
var p = lookUpTable.Ptr(0);
Mat matByLUT = new Mat();
Stopwatch sw;
const int times = 100;
unsafe
{
byte* b = (byte*)p.ToPointer();
for (int i = 0; i < 256; ++i)
b[i] = table[i];
sw = Stopwatch.StartNew();
for (int i = 0; i < times; ++i)
{
var matClone = lena.Clone();//为了与其它算法一样(耗时)
Cv2.LUT(matClone, lookUpTable, matByLUT);
}
}
sw.Stop();
//显示文字
Helper.PutText(matByLUT, $"{times} times ByLUT:{sw.ElapsedMilliseconds} ms");
//显示所有图像
//using (new Window("lena", lena))
using (new Window("ByPtr", matByPtr))
using (new Window("ByGenericIndexer", matByGenericIndexer))
using (new Window("ByIndexer", matByIndexer))
using (new Window("ByUnsafeIndexer", matByUnsafeIndexer))
using (new Window("ByAt", matByAt))
using (new Window("ByGetSet", matByGetSet))
using (new Window("ByLUT", matByLUT))
{
Cv2.WaitKey();
}
}
private Mat DoSIAR(Func<Mat, byte[], Mat> SIARFun, Mat lena, byte[] table, string name)
{
const int times = 100;
Mat mat = null;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < times; ++i)
{
mat = lena.Clone();
mat = SIARFun(mat, table);
}
sw.Stop();
//图像中显示文字
Helper.PutText(mat, $"{times} times {name}:{sw.ElapsedMilliseconds} ms");
return mat;
}
源代码:LearnOpenCVSharp