【图像处理】图像错切变换

一、图像错切原理

图像错切变换在图像几何形变方面非常有用,常见的错切变换分为X方向(水平)Y方向(垂直) 的错切变换。对应的数学矩阵分别如下:

X方向错切矩阵(y坐标不变,x坐标随y变化):

1 k 0 0 1 0 0 0 1 \] \\begin{bmatrix} 1 \& k \& 0 \\\\ 0 \& 1 \& 0 \\\\ 0 \& 0 \& 1 \\end{bmatrix} 100k10001 **Y方向错切矩阵**(x坐标不变,y坐标随x变化): \[ 1 0 0 k 1 0 0 0 1 \] \\begin{bmatrix} 1 \& 0 \& 0 \\\\ k \& 1 \& 0 \\\\ 0 \& 0 \& 1 \\end{bmatrix} 1k0010001 假设 P ( x 1 , y 1 ) P(x_1, y_1) P(x1,y1) 为错切变换前的像素点, P ′ ( x 2 , y 2 ) P'(x_2, y_2) P′(x2,y2) 为变换后像素点: * X方向错切变换: x 2 = x 1 + k ⋅ y 1 x_2 = x_1 + k \\cdot y_1 x2=x1+k⋅y1, y 2 = y 1 y_2 = y_1 y2=y1 * Y方向错切变换: y 2 = y 1 + k ⋅ x 1 y_2 = y_1 + k \\cdot x_1 y2=y1+k⋅x1, x 2 = x 1 x_2 = x_1 x2=x1 其中 k = tan ⁡ ( θ ) k = \\tan(\\theta) k=tan(θ), θ \\theta θ 为错切角度(通常取0\~45度)。 ## 二、opencv实现错切 ### 2.1 实现步骤 基于C++ OpenCV实现错切变换,实现步骤如下: 1. **计算输出图像尺寸**:错切后图像会在水平或垂直方向扩展,需根据错切角度计算新的宽高(超出原图像部分填充背景色)。 2. **像素坐标映射**:遍历输出图像的每个像素,反向计算其在原图像中对应的源像素坐标(避免空洞问题)。 3. **线性插值**:由于源像素坐标可能为小数,通过线性插值计算最终像素值(原Java代码使用线性插值,此处保持一致)。 4. **背景色填充**:若源像素坐标超出原图像范围,填充预设背景色。 ### 2.2 计算错切后图像的宽与高 根据错切方向(垂直/水平)和角度,通过正切函数计算扩展后的尺寸。 * 水平错切(X方向):输出宽度 = 原宽度 + 原高度 × tan ⁡ ( θ ) \\tan(\\theta) tan(θ),高度不变。 * 垂直错切(Y方向):输出高度 = 原高度 + 原宽度 × tan ⁡ ( θ ) \\tan(\\theta) tan(θ),宽度不变。 ```cpp // 角度转弧度(OpenCV中CV_PI表示π) double angle_rad = angle * CV_PI / 180.0; // 计算输出图像宽高 if (vertical) { out_h = static_cast(src_h + src_w * tan(angle_rad)); out_w = src_w; } else { out_w = static_cast(src_w + src_h * tan(angle_rad)); out_h = src_h; } ``` ### 2.3 目标像素坐标映射 反向映射(从输出像素找原像素)可避免变换后图像出现空洞。根据错切方向调整映射公式: * 水平错切(vertical=false): s r c x = d s t x − tan ⁡ ( θ ) × ( d s t y − s r c h ) src_x = dst_x - \\tan(\\theta) \\times (dst_y - src_h) srcx=dstx−tan(θ)×(dsty−srch), s r c y = d s t y src_y = dst_y srcy=dsty * 垂直错切(vertical=true): s r c y = d s t y − tan ⁡ ( θ ) × ( d s t x − s r c w ) src_y = dst_y - \\tan(\\theta) \\times (dst_x - src_w) srcy=dsty−tan(θ)×(dstx−srcw), s r c x = d s t x src_x = dst_x srcx=dstx ```cpp // dst_y:输出图像的行(对应y坐标),dst_x:输出图像的列(对应x坐标) double src_y, src_x; if (vertical) { src_y = dst_y - tan(angle_rad) * (dst_x - src_w); src_x = dst_x; } else { src_x = dst_x - tan(angle_rad) * (dst_y - src_h); src_y = dst_y; } ``` ### 2.4 线性插值计算像素值 处理源像素坐标为小数的情况,通过相邻像素的线性加权(权重 u u u)计算最终像素值,同时处理边界(超出原图像范围填充背景色)。 ```cpp // 计算整数坐标(向下取整) int y0 = static_cast(floor(src_y)); int x0 = static_cast(floor(src_x)); // 计算插值权重 u(小数部分) double u_y = src_y - y0; // 垂直方向权重(仅垂直错切时有效) double u_x = src_x - x0; // 水平方向权重(仅水平错切时有效) double u = vertical ? u_y : u_x; // 边界检查:超出原图像范围返回背景色 if (y0 < 0 || y0 >= src_h || x0 < 0 || x0 >= src_w) { return bg_color; } // 相邻像素坐标(处理边界,避免越界) int y1 = (y0 + 1 >= src_h) ? y0 : y0 + 1; int x1 = (x0 + 1 >= src_w) ? x0 : x0 + 1; // 获取相邻像素的BGR值(OpenCV默认BGR通道) Vec3b p0 = src.at(y0, x0); // 左上角像素 Vec3b p1 = vertical ? src.at(y1, x0) : src.at(y0, x1); // 相邻像素 // 线性插值:p = p0*(1-u) + p1*u Vec3b result; for (int c = 0; c < 3; ++c) { // 遍历B、G、R三个通道 result[c] = static_cast(p0[c] * (1 - u) + p1[c] * u); } return result; ``` ### 2.5 完整代码 完整实现代码如下: ```cpp #include #include #include using namespace cv; using namespace std; class ShearFilter { private: double angle_; // 错切角度(度) Scalar bg_color_; // 背景色(默认黑色,BGR格式) bool vertical_; // 是否垂直错切(true=Y方向,false=X方向) int out_w_; // 输出图像宽度 int out_h_; // 输出图像高度 /** * @brief 计算源像素的线性插值结果 * @param src 原图像 * @param src_y 源图像y坐标(行) * @param src_x 源图像x坐标(列) * @return 插值后的像素值(BGR) */ Vec3b getPixel(const Mat& src, double src_y, double src_x) const { int src_h = src.rows; int src_w = src.cols; double angle_rad = angle_ * CV_PI / 180.0; // 边界检查:超出原图像范围返回背景色 if (src_y < 0 || src_y >= src_h || src_x < 0 || src_x >= src_w) { return Vec3b(static_cast(bg_color_[0]), static_cast(bg_color_[1]), static_cast(bg_color_[2])); } // 计算整数坐标(向下取整) int y0 = static_cast(floor(src_y)); int x0 = static_cast(floor(src_x)); // 计算插值权重(小数部分) double u = vertical_ ? (src_y - y0) : (src_x - x0); // 相邻像素坐标(处理边界,避免越界) int y1 = (y0 + 1 >= src_h) ? y0 : y0 + 1; int x1 = (x0 + 1 >= src_w) ? x0 : x0 + 1; // 获取相邻像素的BGR值 Vec3b p0 = src.at(y0, x0); Vec3b p1 = vertical_ ? src.at(y1, x0) : src.at(y0, x1); // 线性插值计算每个通道 Vec3b result; for (int c = 0; c < 3; ++c) { result[c] = static_cast(round(p0[c] * (1 - u) + p1[c] * u)); } return result; } public: // 构造函数(默认:20度、黑色背景、水平错切) ShearFilter() : angle_(20.0), bg_color_(0, 0, 0), vertical_(false), out_w_(0), out_h_(0) {} // Setter方法 void setAngle(double angle) { angle_ = angle; } void setBgColor(const Scalar& color) { bg_color_ = color; } void setVertical(bool vertical) { vertical_ = vertical; } // Getter方法(获取输出图像尺寸) int getOutWidth() const { return out_w_; } int getOutHeight() const { return out_h_; } /** * @brief 执行错切变换 * @param src 输入图像(CV_8UC3) * @param dst 输出图像(自动创建) */ void filter(const Mat& src, Mat& dst) { if (src.empty() || src.type() != CV_8UC3) { cerr << "输入图像为空或格式错误(需CV_8UC3)!" << endl; return; } int src_h = src.rows; int src_w = src.cols; double angle_rad = angle_ * CV_PI / 180.0; double k = tan(angle_rad); // 错切系数 // 1. 计算输出图像尺寸(保持不变,但用cvRound确保精度) if (vertical_) { out_h_ = cvRound(src_h + src_w * k); // 垂直错切:高度 = 原高 + 原宽×k out_w_ = src_w; } else { out_w_ = cvRound(src_w + src_h * k); // 水平错切:宽度 = 原宽 + 原高×k out_h_ = src_h; } cout << "错切后尺寸:宽=" << out_w_ << ",高=" << out_h_ << endl; // 2. 创建输出图像并初始化背景色(避免随机值) dst = Mat::zeros(out_h_, out_w_, CV_8UC3); dst.setTo(bg_color_); // 显式填充背景色 // 3. 遍历输出图像,计算每个像素的颜色(修正映射公式) for (int dst_y = 0; dst_y < out_h_; ++dst_y) { for (int dst_x = 0; dst_x < out_w_; ++dst_x) { double src_y, src_x; if (vertical_) { // 垂直错切:正确映射公式 src_x = dst_x; // x坐标不变 src_y = dst_y - k * dst_x; // y坐标随x偏移(去掉src_w偏移) } else { // 水平错切:正确映射公式 src_y = dst_y; // y坐标不变 src_x = dst_x - k * dst_y; // x坐标随y偏移(去掉src_h偏移) } // 插值获取像素值并赋值(仅当源坐标有效时覆盖背景) if (src_x >= 0 && src_x < src_w && src_y >= 0 && src_y < src_h) { dst.at(dst_y, dst_x) = getPixel(src, src_y, src_x); } } } } }; // 测试代码 int main() { // 1. 读取输入图像(替换为你的图像路径) Mat src = imread("C:/Users/Lenovo/Pictures/pictures/flower.jpg"); if (src.empty()) { cerr << "无法读取图像!" << endl; return -1; } // 2. 初始化错切滤波器 ShearFilter shear_filter; shear_filter.setAngle(20.0); // 设置错切角度(20度) shear_filter.setBgColor(Scalar(0,0,0));// 背景色:黑色(BGR) shear_filter.setVertical(true); // 水平错切(X方向):false;垂直错切(Y方向):true // 3. 执行错切变换 Mat dst; shear_filter.filter(src, dst); // 4. 显示结果 imshow("原图像", src); imshow("错切变换后", dst); // 5. 保存结果(可选) imwrite("C:/Users/Lenovo/Pictures/pictures/flower_out.jpg", dst); // 等待按键退出 waitKey(0); destroyAllWindows(); return 0; } ``` ## 三、运行结果 原图 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8f0dade100084e019c60dc5a8d5332f6.png) 水平错切30° ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a3d266c079b24de99d931756354d3288.png) 垂直错切20° ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/239c6b1ec18440cca6514b2f83dd63dd.png) 更多资料:

相关推荐
文火冰糖的硅基工坊3 小时前
[人工智能-大模型-85]:大模型应用层 - AI/AR眼镜:华为智能眼镜、苹果智能眼镜、Google Glass智能眼镜的软硬件技术架构
人工智能·华为·ar
wolfseek3 小时前
opencv模版匹配
c++·人工智能·opencv·计算机视觉
犽戾武3 小时前
1. 简单回顾Numpy神经网络
人工智能·神经网络·numpy
Lab4AI大模型实验室3 小时前
【每日Arxiv热文】还在为视频编辑发愁?港科大&蚂蚁集团提出Ditto框架刷新SOTA!
人工智能·计算机视觉·视频编辑·ai agent·智能体学习
阿里云大数据AI技术3 小时前
云栖实录 | 实时计算 Flink 全新升级 - 全栈流处理平台助力实时智能
大数据·人工智能
新加坡内哥谈技术3 小时前
谷歌Quantum Echoes算法:迈向量子计算现实应用的重要一步
人工智能
Godspeed Zhao4 小时前
自动驾驶中的传感器技术70——Navigation(7)
人工智能·机器学习·自动驾驶
这儿有一堆花4 小时前
AI 翻译入门指南:机器如何理解语言
人工智能·web
明月照山海-4 小时前
机器学习周报十九
人工智能·机器学习