当前位置: 首页 > news >正文

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

一、图像错切原理

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

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

[1k0010001]\begin{bmatrix} 1 & k & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}100k10001

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

[100k10001]\begin{bmatrix} 1 & 0 & 0 \\ k & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}1k0010001

假设 P(x1,y1)P(x_1, y_1)P(x1,y1) 为错切变换前的像素点,P′(x2,y2)P'(x_2, y_2)P(x2,y2) 为变换后像素点:

  • X方向错切变换:x2=x1+k⋅y1x_2 = x_1 + k \cdot y_1x2=x1+ky1y2=y1y_2 = y_1y2=y1
  • Y方向错切变换:y2=y1+k⋅x1y_2 = y_1 + k \cdot x_1y2=y1+kx1x2=x1x_2 = x_1x2=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(θ),宽度不变。
// 角度转弧度(OpenCV中CV_PI表示π)
double angle_rad = angle * CV_PI / 180.0;
// 计算输出图像宽高
if (vertical) {out_h = static_cast<int>(src_h + src_w * tan(angle_rad));out_w = src_w;
} else {out_w = static_cast<int>(src_w + src_h * tan(angle_rad));out_h = src_h;
}

2.3 目标像素坐标映射

反向映射(从输出像素找原像素)可避免变换后图像出现空洞。根据错切方向调整映射公式:

  • 水平错切(vertical=false):srcx=dstx−tan⁡(θ)×(dsty−srch)src_x = dst_x - \tan(\theta) \times (dst_y - src_h)srcx=dstxtan(θ)×(dstysrch)srcy=dstysrc_y = dst_ysrcy=dsty
  • 垂直错切(vertical=true):srcy=dsty−tan⁡(θ)×(dstx−srcw)src_y = dst_y - \tan(\theta) \times (dst_x - src_w)srcy=dstytan(θ)×(dstxsrcw)srcx=dstxsrc_x = dst_xsrcx=dstx
// 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 线性插值计算像素值

处理源像素坐标为小数的情况,通过相邻像素的线性加权(权重 uuu)计算最终像素值,同时处理边界(超出原图像范围填充背景色)。

// 计算整数坐标(向下取整)
int y0 = static_cast<int>(floor(src_y));
int x0 = static_cast<int>(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<Vec3b>(y0, x0);  // 左上角像素
Vec3b p1 = vertical ? src.at<Vec3b>(y1, x0) : src.at<Vec3b>(y0, x1);  // 相邻像素// 线性插值:p = p0*(1-u) + p1*u
Vec3b result;
for (int c = 0; c < 3; ++c) {  // 遍历B、G、R三个通道result[c] = static_cast<uchar>(p0[c] * (1 - u) + p1[c] * u);
}
return result;

2.5 完整代码

完整实现代码如下:


#include <opencv2/opencv.hpp>
#include <cmath>
#include <iostream>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<uchar>(bg_color_[0]),static_cast<uchar>(bg_color_[1]),static_cast<uchar>(bg_color_[2]));}// 计算整数坐标(向下取整)int y0 = static_cast<int>(floor(src_y));int x0 = static_cast<int>(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<Vec3b>(y0, x0);Vec3b p1 = vertical_ ? src.at<Vec3b>(y1, x0) : src.at<Vec3b>(y0, x1);// 线性插值计算每个通道Vec3b result;for (int c = 0; c < 3; ++c) {result[c] = static_cast<uchar>(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);  // 垂直错切:高度 = 原高 + 原宽×kout_w_ = src_w;} else {out_w_ = cvRound(src_w + src_h * k);  // 水平错切:宽度 = 原宽 + 原高×kout_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<Vec3b>(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;
}

三、运行结果

原图
在这里插入图片描述

水平错切30°

在这里插入图片描述

垂直错切20°
在这里插入图片描述
更多资料:https://github.com/0voice

http://www.dtcms.com/a/532405.html

相关文章:

  • Docker环境离线安装-linux服务器
  • 软件设计师知识点总结:结构化开发
  • 持续改变源于团队学习
  • Unity安装newtonsoft
  • Spring Boot3零基础教程,整合 Redis,笔记69
  • 凡科网站官网登录入口wordpress 列表模板
  • 读论文AI prompt
  • 《Ionic 平台:全面解析与深入探讨》
  • 网站做淘宝客有什么要求微网站的优势
  • FFmpeg 基本数据结构 AVStream分析
  • kafka数据同步一致性问题
  • 淘宝网站的建设内容网站建设的平台
  • Linux进程间通信:管道与System V IPC的全解析
  • 技术选型对比:几种主流方案获取淘宝商品实时数据的优劣分析
  • 想象力网站建设深圳市房地产信息系统平台
  • 让 Codex 用 React 生成一个博客首页
  • Kafka的概念
  • 开发实战 - ego商城 - 补充:小程序性能优化
  • 如何截取PDF内容为图片
  • 智慧景区导览小程序
  • 58同城企业网站怎么做的wordpress 评论设计
  • 珠海建站模板怎么做多语言网站
  • 致敬哈耶克,让灯火照亮个人前行的道路
  • 【LeetCode100】--- 96.只出现一次的数字【思维导图+复习回顾】
  • 网络编程Socket套接字
  • 算法基础篇(9)倍增与离散化
  • 搓了一个Deepin15的兼容环境(也支持Deepin20/23)
  • C++ - C++11拓展
  • php 装修网站柳州搜索引擎营销平台
  • Householder变换:线性代数中的镜像反射器