opencv的Mat通道交换

本文详细介绍使用OpenCV进行图像通道操作的四种方法,包括直接遍历数组元素、使用split和merge函数、利用mixChannels函数以及针对Blob的通道交换。通过实例代码展示不同方法的应用场景及优劣对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先以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;
}

三个重要的数据:

  1. srcImg为原Blob
  2. dstImg为创建空的目标Blob,用来保存交换通道后的数据
  3. 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]通道, 另外两个通道分离情况类似。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值