先以3通道为例,描述多种方式;后以常规Blob的4维(N×C×H×W, C=3的BGR图像)数组进行演示。
1、常规实现
遍历数组元素 Mat 数据交换
#include <vector>
#include <iostream>
#include "opencv2/opencv.hpp"
int main()
{
using std::cout;
using std::endl;
uchar dat[] = { 1, 7,13, 2, 8,14, 3, 9,15,
4,10,16, 5,11,17, 6,12,18 };
Mat m(2, 3, CV_8UC3, dat); // 2x3, 3
cout << m << endl;
//[ 1, 7, 13, 2, 8, 14, 3, 9, 15;
// 4, 10, 16, 5, 11, 17, 6, 12, 18]
for (int i = 0; i < m.rows; ++i) {
for (int j = 0; j < m.cols; ++j) {
Vec3b& pix = m.at<Vec3b>(i, j); //指针或引用
std::swap(pix[0], pix[2]);
}
}
cout << m << endl;
//[ 13, 7, 1, 14, 8, 2, 15, 9, 3;
// 16, 10, 4, 17, 11, 5, 18, 12, 6]
return 0;
}
使用ImageWatch查看交换前后的结果
2、使用split和merge
先使用split获得各通道数组,再用merge进行调整后通道数组合并
使用一下代码代替for循环部分,效果同1的效果。
std::vector<Mat> chs;
split(m, chs);
std::swap(chs[0], chs[2]);
merge(chs, m);
三通道图像分离的函数原型为CV_EXPORTS_W void split(InputArray m, OutputArrayOfArrays mv)
。
3、使用mixChannel
用mixChannel进行通道数组合并,也可以提取通道,任意组合等…
注意,cv::split,cv::merge,cv::extractChannel,cv::insertChannel and some forms of cv::cvtColor are partial cases of cv::mixChannels.
Mat dstImg(m.size(), CV_8UC3); //这里是交换通道,所有结果图类型不变
Mat out[] = { dstImg };
int from_to[] = { 0,2, 1,1, 2,0};
// dstImg、out和 from_to 对应
// m[0] -> dstImg[2], m[1] -> dstImg[1], m[2] -> dstImg[0]
cv::mixChannels(&m, 1, out, 1, from_to, 3);
结果同上。
另外: 这里mixChannel还可以进行通道分离组合生成新的矩阵,例如 m[2]、m[0]为一个图,m[1]为一个图。(还可以舍弃某一些通道)
Mat dstImg1(m.size(), CV_8UC2); //用于放 m[2]、m[0], 所以为 8UC2
Mat dstImg2(m.size(), CV_8UC1); //用于放 m[1], 所以为 8UC1
Mat out[] = { dstImg1, dstImg2 };
int from_to[] = { 0,1, 1,2, 2,0};
// dstImg、out和 from_to 对应
// m[0] -> dstImg1[1], m[1] -> dstImg2[0], m[2] -> dstImg1[0]
cv::mixChannels(&m, 1, out, 2, from_to, 3);
原图和2个结果图像如下:
原图3个通道; 第一个结果图为2,0通道结果; 第二个结果图为1通道结果。
4、纬度为4的Blob通道交换
Blob的4维(N×C×H×W)需要交换C。
可以对通道使用指针的方式进行变换数据,或者使用split重载方法。
(1)遍历元素,直接进行交换通道
#include <vector>
#include <iostream>
#include "opencv2/opencv.hpp"
int main()
{
using std::cout;
using std::endl;
// 创建一个Blob,
std::vector<int> sz = { 1,3,2,4 }; // 一副图,3通道, 大小2*4
Mat srcImg; // blob
srcImg.create(sz, CV_32FC1); //为了使用 dnn的imagesFromBlob函数
randu(srcImg, 0, 200);
Mat srcImgcopy = srcImg.clone();
std::vector<cv::Mat> srcImgcopyBlobs;
cv::dnn::imagesFromBlob(srcImgcopy, srcImgcopyBlobs); // 交换前
for (int i = 0; i < srcImgcopy.size[0]; ++i) { //1
//for (int j = 0; j < srcImgcopy.size[1]; ++j) { // 3
for (int k = 0; k < srcImgcopy.size[2]; ++k) { // 2
for (int l = 0; l < srcImgcopy.size[3]; ++l) { // 2
float* ch0 = (float*)(srcImgcopy.ptr(i, 0, k) + l* srcImgcopy.elemSize());
float* ch2 = (float*)(srcImgcopy.ptr(i, 2, k) + l* srcImgcopy.elemSize());
std::swap(*ch0,*ch2);
}
}
//}
}
cv::dnn::imagesFromBlob(srcImgcopy, srcImgcopyBlobs); //交换后
ch0、ch2分别是 (k,l)位置的2个通道的数据指针,也可以用.dat进行计算指针,需要注意元素占用的字节数elemSize()。还可也用如下.at方式进行交换
cv::Vec4i pos0 = { i,0,k,l };
cv::Vec4i pos2 = { i,2,k,l };
std::swap(srcImgcopy.at<float>(pos0), srcImgcopy.at<float>(pos2));
内存中的数据按照plane排列,3个通道用3种颜色标注,代码打印、内存查看格式如下
for (int i = 0; i < srcImgcopy.size[0]; ++i) {
printf("i=%d %x\n", i, srcImgcopy.ptr(i));
for (int j = 0; j < srcImgcopy.size[1]; ++j) {
printf(" j=%d %x\n", j, srcImgcopy.ptr(i,j));
for (int k = 0; k < srcImgcopy.size[2]; ++k) {
printf(" k=%d %x\n", k, srcImgcopy.ptr(i, j, k ));
}
}
}
交换前后可视化的矩阵数据如下(使用imagesFromBlob拿到的Mat图像是内存结构变换过后的,二维图,opencv的图像格式):
(2)使用split进行交换通道(推荐)
对于小size的Blob用(1)中像素遍历交换通道数据的代码执行效率较快,但是大size的情况用这里的方法要快得多。
#include <vector>
#include <iostream>
#include "opencv2/opencv.hpp"
int main(int argc, char** argv)
{
// 创建一个Blob,
std::vector<int> sz = { 1,3,2,4 }; // 一副图,3通道, 大小2*4
Mat srcImg; // blob
srcImg.create(sz, CV_32FC1); //为了使用 dnn的imagesFromBlob函数
randu(srcImg, 0, 200);
// Blob转换二维矩阵, N=1
std::vector<cv::Mat> srcBlobImgs;
cv::dnn::imagesFromBlob(srcImg, srcBlobImgs);
Mat srcImg_ = srcBlobImgs[0];
int nch = srcImg_.channels();
int ddepth = srcImg_.depth();
Size size = srcImg_.size();
// 最重要部分: 交换blob的通道代码
Mat dstImg; // blob
int ssz[] = { 1, nch, size.height, size.width };
dstImg.create(4, ssz, ddepth);
Mat ch[3];
for (int j = 0; j < nch; j++)
ch[j] = Mat(size, ddepth, dstImg.ptr(0, j));
std::swap(ch[0], ch[2]); //数据区指针交换
cv::split(srcImg_, ch);
// dstImg_ 交换后的结果图
std::vector<cv::Mat> dstBlobImgs;
cv::dnn::imagesFromBlob(dstImg, dstBlobImgs);
Mat dstImg_ = dstBlobImgs[0];
return 0;
}
三个重要的数据:
- srcImg为原Blob
- dstImg为创建空的目标Blob,用来保存交换通道后的数据
- ch[3]为引用dstImg的三个通道数据的图像的数组
先经过std::swap(ch[0], ch[2]);
后,数组ch[3]中的三个Mat的数据指针指向为原Blob图像通道2、1、0的内存数据块。
再使用split将原Blob提取图像Mat(二维,opencv的图像格式)的3个通道分离到ch[3]数组中。这里使用的split原型为CV_EXPORTS void split(const Mat& src, Mat* mvbegin);
,不同于三通道图像分离的CV_EXPORTS_W void split(InputArray m, OutputArrayOfArrays mv);
。
进行通道分离时, srcImg的[0]通道分离到ch[0]中,而ch[0]的数据引用自dstImg的[2]通道,实际效果为 rcImg的[0]通道 分离到 dstImg的[2]通道, 另外两个通道分离情况类似。