介绍卷积的作用和原理的文章很多,此处就不再赘述,以防自己的理解不够而误解了别人。此篇文章主要是介绍C++实现卷积的三种操作。
需要注意的是,‘same’和‘full’只是将被卷积的矩阵做了相应尺寸的0填充后再进行‘valid’卷积即可。
此处默认步长1。
三种卷积操作的示意图:
Valid:
Same:
Full:
源码:
#include <iostream>
#include <map>
#include <math.h>
class OpMatrix {
private:
const enum CONV_TYPE {
ID_full = 0,
ID_valid,
ID_same
};
std::map<std::string, int> convtype_str_id = std::map<std::string, int>();
int matrix_w, matrix_h, corner_length;
public:
OpMatrix() {
convtype_str_id.insert(std::pair<std::string, int>("full", ID_full));
convtype_str_id.insert(std::pair<std::string, int>("same", ID_same));
convtype_str_id.insert(std::pair<std::string, int>("valid", ID_valid));
}
// 矩阵乘法运算
template <typename _Tp>
_Tp* mul(_Tp* matrix_a, _Tp* matrix_b, int a_w, int a_h, int b_w, int b_h) {
// 申请矩阵乘法运算结果的内存空间
_Tp* result = (_Tp*)malloc(sizeof(_Tp)*a_h*b_w);
// 矩阵相乘需满足前矩阵的列数等于后矩阵的行数
if (a_w != b_h) {
throw "不符合矩阵相乘规则";
return result;
}
else {
// 遍历前矩阵的行
for (int i = 0; i < a_h; i++) {
// 遍历后矩阵的列
for (int j = 0; j < b_w; j++) {
result[i*b_w + j] = 0;
// 前矩阵的每一行与后矩阵的每一列内积,将结果存放在result中
for (int z = 0; z < a_w; z++) {
result[i*b_w + j] += (matrix_a[i*a_w + z] * matrix_b[z*b_w + j]);
}
}
}
}
// 打印计算结果
for (int i = 0; i < a_h*b_w; i++) {
std::cout << result[i] << std::endl;
}
return result;
}
// 矩阵逆时针旋转
template <typename _Tp>
_Tp* rot90(_Tp* matrix, int length, int nums,bool show) {
// 输入合法性检测
if (nums < 0) {
throw "请输入非负整数!";
}
// 逆时针旋转90度次数整理
nums = nums % 4;
// 为了提高计算效率,对四次不同的旋转次数做直接变换,不使用递归调用逆时针90度旋转的函数
switch (nums)
{
case 0:
break;
case 1:
break;
case 2:
for (int i = 0; i < length / 2; i++) {
for (int j = 0; j < length; j++) {
_Tp temp = matrix[i*length + j];
matrix[i*length + j] = matrix[(length - i - 1)*length + (length - j - 1)];
matrix[(length - i - 1)*length + (length - j - 1)] = temp;
}
}
// 如果corner的边长为奇数,则需将中间的那行数据颠倒
if (length % 2 != 0) {
for (int i = 0; i < length / 2; i++) {
_Tp temp = matrix[(length / 2)*length + i];
matrix[(length / 2)*length + i] = matrix[(length / 2)*length + (length - i - 1)];
matrix[(length / 2)*length + (length - i - 1)] = temp;
}
}
break;
case 3:
break;
default:
break;
}
if (show) {
showMatrix(matrix, length, length);
}
return matrix;
}
// 矩阵卷积运算
template <typename _Tp>
void conv(_Tp* matrix, _Tp* corner, int matrix_w, int matrix_h, int corner_length,std::string conv_type, _Tp* result,bool show,bool is_rot) {
int result_w = matrix_w - corner_length + 1;
int result_h = matrix_h - corner_length + 1;
// 卷积核逆时针旋转180度
if (is_rot) {
corner = rot90(corner, corner_length, 2, false);
}
int conv_key = convtype_str_id.at(conv_type);
switch (conv_key)
{
case ID_valid:
// 卷积结果的尺寸((matrix_h - corner_length + 1),(matrix_w - corner_length + 1))
result_w = matrix_w - corner_length + 1;
result_h = matrix_h - corner_length + 1;
// 申请矩阵卷积的运算结果的内存空间
result = (_Tp*)malloc(sizeof(_Tp)*result_w*result_h);
for (int i = 0; i < result_h; i++) {
for (int j = 0; j < result_w; j++) {
result[i*result_w + j] = 0;
for (int z = 0; z < corner_length; z++) {
for (int q = 0; q < corner_length; q++) {
result[i*result_w + j] += matrix[(i + z)*matrix_w + j + q] * corner[z*corner_length + q];
}
}
}
}
break;
case ID_full:
result = (_Tp*)malloc(sizeof(_Tp)*(matrix_w + corner_length - 1)*(matrix_h + corner_length - 1));
// 如果corner的边长为奇数,则用corner的中心点对应matrix的扫描到的点进行卷积运算
if (corner_length % 2 == 0) {
std::cout << "same型卷积要求卷积核尺寸为奇数" << std::endl;
return;
}
else {
// 创建新的matrix_new,用0填充原始matrix
_Tp* matrix_new = padMatrix(matrix, matrix_w, matrix_h, matrix_w + 2 * corner_length - 2, matrix_h + 2 * corner_length - 2, false);
// 对matrix_new进行full卷积,变相实现same型卷积
conv(matrix_new, corner, matrix_w + 2 * corner_length - 2, matrix_h + 2 * corner_length - 2, corner_length, "valid", result, show, false);
// 释放原始matrix
// free(matrix);
return;
}
break;
case ID_same:
result = (_Tp*)malloc(sizeof(_Tp)*matrix_w*matrix_h);
// 如果corner的边长为奇数,则用corner的中心点对应matrix的扫描到的点进行卷积运算
if (corner_length % 2 == 0) {
std::cout << "same型卷积要求卷积核尺寸为奇数" << std::endl;
return;
}
else {
// 创建新的matrix_new,用0填充原始matrix
_Tp* matrix_new = padMatrix(matrix, matrix_w, matrix_h, matrix_w + corner_length - 1, matrix_h + corner_length - 1, false);
// 对matrix_new进行full卷积,变相实现same型卷积
conv(matrix_new, corner, matrix_w + corner_length - 1, matrix_h + corner_length - 1, corner_length, "valid", result, show, false);
// 释放原始matrix
// free(matrix);
return;
}
break;
default:
throw "请输入正确的卷积操作类型!";
return;
}
// 打印计算结果
if (show) {
showMatrix(result, result_w, result_h);
}
}
template <typename _Tp>
_Tp* padMatrix(_Tp* matrix, int matrix_w, int matrix_h, int matrix_w_new, int matrix_h_new , bool show) {
if (matrix_w_new - matrix_w != matrix_h_new - matrix_h || (matrix_h_new - matrix_h) % 2 != 0) {
std::cout << "目标矩阵增加的长和宽需相同,且增加的行和列需要能被2整除" << std::endl;
exit(0);
}
// 根据目标矩阵的长宽申请内存
_Tp* matrix_new = (_Tp*)malloc(sizeof(_Tp)*matrix_w_new*matrix_h_new);
// 计算原matrix在matrix_new中的位置
int top = (matrix_h_new - matrix_h) / 2;
int bottom = matrix_h_new - 1 - top;
int left = top;
int right = matrix_w_new - 1 - top;
int matrix_index = 0;
// 遍历新数组,对每个元素进行赋值
for (int i = 0; i < matrix_h_new; i++) {
for (int j = 0; j < matrix_w_new; j++) {
// 如果遍历到原matrix在matrix_new中的位置,进行赋值
if (i >= top && i <= bottom && j >= left && j <= right) {
matrix_new[i*matrix_w_new + j] = matrix[matrix_index];
matrix_index++;
}
else {
// 填充0
matrix_new[i*matrix_w_new + j] = 0;
}
}
}
if (show) {
showMatrix(matrix_new, matrix_w_new, matrix_h_new);
}
return matrix_new;
}
template <typename _Tp>
void showMatrix(_Tp matrix, int matrix_w, int matrix_h) {
for (int i = 0; i < matrix_h; i++) {
for (int j = 0; j < matrix_w; j++) {
std::cout << matrix[i*matrix_w + j] << " ";
}
std::cout << std::endl;
}
}
void test_mul() {
int a[8] = { 1,2,3,4,4,5,6,8 };
int b[8] = { 1,2,1,2,1,2,1,2 };
std::cout << mul<int>(a, b, 4, 2, 2, 4) << std::endl;
double a1[8] = { 1.1,2.2,3.3,4.4,4.4,5.5,6.6,8.8 };
double b1[8] = { 1.1,2.2,1.1,2.2,1.1,2.2,1.1,2.2 };
std::cout << mul<double>(a1, b1, 4, 2, 2, 4) << std::endl;
}
void test_conv() {
int matrix[25] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 };
int corner[9] = { 1,2,1,1,2,1,1,2,1 };
int* result=new int();
conv<int>(matrix, corner, 5, 5, 3, "valid", result, true, true);
}
void test_rot() {
int matrix[25] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 };
rot90<int>(matrix, 5, 2, true);
}
};