关于我用C#写Opencv这件事

今天我们神秘的数字图像处理做实验了
我使用C#配合Opencvsharp4做实验
虽然Opencvsharp4宣称是和C++的API是对应的
但实际上的开发体验差别真挺大的,很多API的调用方法是不一样的
(有些调用像C++,有些调用有自己的特色)
基本上所有的代码都要自己去试,写着挺痛苦的
所以还是讲讲我们做了啥吧

<0x00>读取图像

这个其实是简单的
假设需要读取一个图片,比方说是D:/Test/1.png
通过Cv2.ImRead()方法就可以读取为内置的矩阵类型了

Mat image = Cv2.ImRead("D:/Test/1.png");

这个方法可以读取一般常见的图片格式

然后,这个方法还可以传入第二个参数,用来控制读取的行为
比方说我们希望获取的是图像的灰度图

Mat grayImage = Cv2.ImRead("D:/Test/1.png", ImreadModes.Grayscale);

ImreadModes是一个枚举类型,有很多常用的读取行为
一般也就是这个ImreadModes.Grayscale获取灰度图用的多
Cv2.ImRead()这个参数的默认值是ImreadModes.Color,即默认读入带颜色的图像

<0x01>将有色图转换为灰度图

前面提到,我们可以指定读取图片时保存为灰度图
那有没有别的在运行时从有色图转换为灰度图的方法呢?
肯定是有的

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat grayImage = iamge.CvtColor(ColorConversionCodes.RGB2GRAY);

这样也完成了转换

还有一个等价的写法

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat grayImage = new();
Cv2.CvtColor(image, grayImage, ColorConversionCodes.RGB2GRAY);

这样写也行

CvtColor()方法中,传入的ColorConversionCodes也是个枚举类型
里面也有很多常用的转换方式,有什么RGB2BGR之类的

<0x02>图像按位取反

这个在C#中的写法确实跟C++之类的挺像的

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat inversedImage = new();
Cv2.BitwiseNot(image, inversedImage);

当然也可以自己写对每个位的操作,但这样就太麻烦了

<0x03>图像对比度增强

关于图像对比度增强的办法有很多,我这里用的是限制对比度自适应直方图均衡化的办法
(原理我也说不出来,没细学,还是看别的大佬的文章吧)

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat enhancedImage = new();

CLAHE clahe = Cv2.CreateCLAHE(3, new OpenCvSharp.Size(8, 8));
clahe.Apply(image, enhancedImage);

第一个参数表示像素出现的阈值,应该是百分比阈值,默认是40
第二个参数表示重新分配的大小

限制对比度自适应直方图均衡化的大体过程如下
首先会统计每种像素的出现次数
然后跟设置的阈值对比,如果超过阈值,会把多余的像素按设定的大小重新分配给别的类型的像素
总之确实是能增强对比度(真说不大明白)

<0x04>图像二值化

所谓二值化算是极端化的灰度图,只有黑白两种颜色
代码如下

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat binarizedImage = image.Threshold(127, 255, ThresholdTypes.Binary);

也有稍微麻烦点的写法

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat binarizedImage = new();
Cv2.Threshold(image, binarizedImage, 127, 255, ThresholdTypes.Binary);

Threshold()第一个数字是阈值,第二个值算是默认值,具体行为看第三个参数
ThresholdTypes是一个枚举类型,表示如何处理图像
二值化的时候这个值取ThresholdTypes.Binary,表示像素值大于阈值时用默认值,否则赋值0
比方说在二值化的时候,某像素值为128>127,那么就会把它的值变为255
其他的行为这里就不展开了

<0x05>图片相加

同大小图片相加

这个其实很简单,毕竟在Opencv眼里,所有的图片都是矩阵,相加不是很简单嘛

Mat A = Cv2.ImRead("D:/Test/1.png");
Mat B = Cv2.ImRead("D:/Test/2.png");

Mat addedImage = A + B;

(就这样写就可以了)

不嫌烦的话还有下面的写法

Mat addedImage = A.Add(B);
Mat addedImage = new();
Cv2.Add(A, B, addedImage);

不同大小的图片相加

这些Add()方法要求矩阵大小一致,相当于要求图片大小一致

那如果我们就是要不同大小图片相加,就要多做些处理
我的处理方式是
先在比较大的图片中扣出跟小图片一样大小的部分
抠出的图和小图片相加,然后再替换掉大图片扣掉的部分

怎么抠出图像

Opencvsharp4中有个Rect类型,可以用来划出需要的部分
类型声明示例

Rect rect = new(0, 0, 100, 100);

前面表示起始位置,也即矩形的左上角的点坐标,后面两个参数表示矩形大小

对于一个图像,假设我们需要在图像(100, 100)的地方扣出200*200大小的图片
代码如下

Mat image = Cv2.ImRead("D:/Test/1.png");
Mat temp = image[new Rect(100, 100, 200, 200)];

需要注意的是,这里的temp获取到的是image对应区域的引用
temp的所有修改也会反应到image
如果需要新建一个矩阵,需要用CopyTo()方法

完整的代码

有了上面的基础,现在给出完整的图片相加代码

Mat L = Cv2.ImRead("D:/Test/1.png");
Mat S = Cv2.ImRead("D:/Test/2.png");

//还需要注意框选的区域不能超过大图片的范围
Mat temp = image[new Rect(100, 100, S.Width, S.Height)];
temp = temp + s;

这样就可以了,至于图片相减也是差不多这样的代码

Licensed under CC BY-NC-SA 4.0