C++ OpenCV制作哈哈镜效果:凸透镜与凹透镜像素映射详解
使用C++和OpenCV库,通过像素坐标的数学映射算法,可以轻松在图像上实现凸透镜放大与凹透镜缩小的哈哈镜特效。本文详解核心变换原理与代码实现。
实现原理与准备
哈哈镜效果的本质是图像像素空间的非线性扭曲。在二维坐标系中,我们需要计算输入图像中心点 (cx, cy),以及画布上任意像素点 (x, y) 到中心的相对距离 dx = x - cx 和 dy = y - cy。根据预先设定的变换半径或压缩系数,计算出目标像素在原始图像中的对应位置,并将颜色值提取填充至新的画布上。
第一步:配置开发环境与读取图像
确保已安装 C++ 编译器及 OpenCV 库。主程序流程首先加载本地图片文件进入内存,并检查有效性:
- 入口与准备:定义
cv::Mat src,调用imread()读取图片。 - 动作:校验
src.empty(),若为空则提示错误并终止。 - 观察状态:成功加载后,控制台打印提示,内存中存在包含图像数据的矩阵对象。
基础代码结构
int main()
{
// 读取测试图片
cv::Mat src = cv::imread("test.jpg");
if (src.empty()) {
std::cout << "No Image!" << std::endl;
return -1;
}
// 后续处理...return 0;
}第二步:生成凸透镜(放大)效果
凸透镜特效使图像中心区域的物体看起来被放大。其核心是利用极坐标变换思想,随着距离中心越远,像素偏移量越大。具体计算公式为:x_new = (dx / 2) * (sqrt(dx² + dy²) / r) + cxy_new = (dy / 2) * (sqrt(dx² + dy²) / r) + cy
核心逻辑与操作
- 初始化画布:创建与原图尺寸相同的零矩阵
canvas用于存储新图像。 - 设定参数:确定中心点
cx, cy和控制弯曲强度的半径radius(例如设为 200)。 - 遍历绘制:双重循环遍历输出图像每个像素。
- 计算当前像素相对中心的偏移dx, dy。
- 代入上述公式求出新位置new_x, new_y。
- 安全检查:必须验证新位置是否在原图边界内。 - 像素赋值:将原图在新位置的 BGR 通道颜色拷贝到画布当前位置。
完整函数实现
bool Mirror_Magnify(cv::Mat src)
{
// 创建画布
cv::Mat canvas = cv::Mat::zeros(src.size(), src.type());
int cx = src.cols / 2;
int cy = src.rows / 2;
int radius = 200; // 决定弯曲程度
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
int dx = j - cx;
int dy = i - cy;
// 计算源图像对应坐标
int x = (dx / 2) * (std::sqrt(std::pow(dx, 2) + std::pow(dy, 2)) / radius) + cx;
int y = (dy / 2) * (std::sqrt(std::pow(dx, 2) + std::pow(dy, 2)) / radius) + cy;
// 边界检查与赋值
if ((x > 0 && x < src.cols) && (y > 0 && y < src.rows)) {
cv::Vec3b pixel = src.at<cv::Vec3b>(y, x);
canvas.at<cv::Vec3b>(i, j)[0] = pixel[0];
canvas.at<cv::Vec3b>(i, j)[1] = pixel[1];
canvas.at<cv::Vec3b>(i, j)[2] = pixel[2];
}
}
}
cv::imshow("Mirror_Magnify", canvas);
return true;
}第三步:生成凹透镜(缩小)效果
凹透镜特效使图像看起来向内凹陷收缩。该模式使用了对数缩放的思想,使得边缘像素被剧烈压缩而挤入画面中心:
x = sqrt(sqrt(dx² + dy²)) * compress * cos(atan2(dy, dx)) + cxy = sqrt(sqrt(dx² + dy²)) * compress * sin(atan2(dy, dx)) + cy
操作要点
- 引入压缩因子:增加一个
compress变量(如 12),用于调整形变的剧烈程度。 - 坐标转换:利用
atan2获取角度,结合模长进行三角函数映射。 - 效果呈现:图像中间区域显得更为深远狭窄,四周景物向中心汇聚。
完整函数实现
bool Mirror_Narrow(cv::Mat src)
{
cv::Mat canvas = cv::Mat::zeros(src.size(), src.type());
double compress = 12.0; // 压缩强度
int cx = src.cols / 2;
int cy = src.rows / 2;
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
int dx = j - cx;
int dy = i - cy;
// 计算极坐标形式的源图像对应坐标
int x = std::sqrt(std::sqrt(std::pow(dx, 2) + std::pow(dy, 2))) * compress * std::cos(std::atan2(dy, dx)) + cx;
int y = std::sqrt(std::sqrt(std::pow(dx, 2) + std::pow(dy, 2))) * compress * std::sin(std::atan2(dy, dx)) + cy;
// 边界检查与赋值
if ((x > 0 && x < src.cols) && (y > 0 && y < src.rows)) {
cv::Vec3b pixel = src.at<cv::Vec3b>(y, x);
canvas.at<cv::Vec3b>(i, j)[0] = pixel[0];
canvas.at<cv::Vec3b>(i, j)[1] = pixel[1];
canvas.at<cv::Vec3b>(i, j)[2] = pixel[2];
}
}
}
cv::imshow("Mirror_Narrow", canvas);
return true;
}第四步:运行验证与排错指南
在 Visual Studio 或其他 IDE 中编译代码并运行窗口查看结果。
常见问题排查
- 画面全黑/空白:通常是因为在赋值时未做
if越界判断,导致访问了非法内存地址而崩溃,或者越界判定条件写反。确保检查x > 0 && x < src.cols。 - 图像闪烁:如果在循环内部频繁调用
imshow可能导致效率低下。建议将所有效果计算完成后,统一展示或通过按钮触发。 - 颜色错位:OpenCV 默认通道顺序为 B-G-R (
Vec3b),而非 RGB。在处理单通道或多通道分离时需特别注意索引 [0], [1], [2] 的对应关系。
总结
以上便是利用 C++ 配合 OpenCV 制作哈哈镜(包括凸透镜与凹透镜效果)的详细内容。通过理解像素重映射的基本原理,你可以进一步修改 radius 或 compress 等参数来探索更多复杂的图像变形艺术。更多关于 C++ 图像处理的技术资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









