You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

初学者求助:如何用OpenCV(C++)实现图像中心漩涡效果

实现图像中心漩涡效果的OpenCV C++方案

核心思路

漩涡效果的本质是对图像像素坐标做非线性扭曲变换——越靠近中心的像素,扭曲程度越大。比起你最初考虑的逐次旋转平移,直接通过坐标变换实现的方法更高效、效果也更自然,不用反复计算局部区域的旋转参数。

具体坐标变换公式推导

假设图像中心坐标为 (cx, cy),针对每个像素 (x, y),我们可以通过极坐标转换来实现漩涡扭曲:

  1. 计算当前像素到中心的距离:r = sqrt((x - cx)^2 + (y - cy)^2)
  2. 计算像素相对于中心的角度:theta = atan2(y - cy, x - cx)
  3. 给角度添加和距离成正比的偏移量(距离越近,偏移越多):theta_new = theta + k * (r / max_r) * CV_PI,其中 k 是漩涡强度系数(比如取1.0-2.0),max_r 是图像角落到中心的最大距离,用来归一化偏移量避免边缘过度扭曲
  4. 将新的极坐标转回笛卡尔坐标:
    x_new = cx + r * cos(theta_new)
    y_new = cy + r * sin(theta_new)
    
  5. 用原图像中 (x_new, y_new) 位置的像素值填充当前像素(注意边界判断,超出范围的像素可设为黑色)

可直接运行的OpenCV C++代码

下面是完整实现,包含平滑插值处理,避免锯齿边缘:

#include <opencv2/opencv.hpp>
#include <cmath>

using namespace cv;
using namespace std;

// 双线性插值获取平滑像素值(比直接取整效果好)
Vec3b bilinearInterpolate(const Mat& img, double x, double y) {
    int x1 = static_cast<int>(floor(x));
    int x2 = x1 + 1;
    int y1 = static_cast<int>(floor(y));
    int y2 = y1 + 1;

    double fx = x - x1;
    double fy = y - y1;

    Vec3b p11 = img.at<Vec3b>(y1, x1);
    Vec3b p12 = img.at<Vec3b>(y2, x1);
    Vec3b p21 = img.at<Vec3b>(y1, x2);
    Vec3b p22 = img.at<Vec3b>(y2, x2);

    Vec3b result;
    for (int c = 0; c < 3; ++c) {
        double val = (1-fx)*(1-fy)*p11[c] + fx*(1-fy)*p21[c] + (1-fx)*fy*p12[c] + fx*fy*p22[c];
        result[c] = static_cast<uchar>(round(val));
    }
    return result;
}

Mat applySwirl(const Mat& src, double strength) {
    Mat dst = Mat::zeros(src.size(), src.type());
    int cx = src.cols / 2;
    int cy = src.rows / 2;
    double maxDist = sqrt(static_cast<double>(cx*cx + cy*cy));

    for (int y = 0; y < src.rows; ++y) {
        for (int x = 0; x < src.cols; ++x) {
            double dx = x - cx;
            double dy = y - cy;
            double r = sqrt(dx*dx + dy*dy);
            double theta = atan2(dy, dx);

            // 计算偏移后的角度,归一化避免边缘过度扭曲
            double newTheta = theta + strength * (r / maxDist) * CV_PI;

            double newX = cx + r * cos(newTheta);
            double newY = cy + r * sin(newTheta);

            // 边界判断,仅处理原图像范围内的像素
            if (newX >= 0 && newX < src.cols - 1 && newY >= 0 && newY < src.rows - 1) {
                dst.at<Vec3b>(y, x) = bilinearInterpolate(src, newX, newY);
            } else {
                // 超出范围设为黑色,也可改为复制边缘像素
                dst.at<Vec3b>(y, x) = Vec3b(0, 0, 0);
            }
        }
    }
    return dst;
}

int main() {
    Mat src = imread("your_image.jpg");
    if (src.empty()) {
        cout << "无法读取图像,请检查路径!" << endl;
        return -1;
    }

    // 调整strength参数控制漩涡强度,1.0-2.0效果较自然
    Mat swirlImg = applySwirl(src, 1.5);

    imshow("原图", src);
    imshow("漩涡效果", swirlImg);
    waitKey(0);
    imwrite("swirl_result.jpg", swirlImg);
    return 0;
}

关键细节说明

  • 双线性插值:如果直接对变换后的坐标取整,会出现明显的锯齿边缘,用双线性插值能让漩涡效果更平滑。
  • 强度控制:strength 参数越大,漩涡扭曲越剧烈;用 r/maxDist 归一化后,边缘像素的扭曲程度会和中心保持比例,避免局部变形突兀。
  • 边界处理:变换后超出原图像范围的像素直接设为黑色,你也可以根据需求改为复制最近的边缘像素,让画面更完整。

效果匹配:最终生成的图像会呈现中心区域螺旋扭曲,向外逐渐减弱的漩涡效果,和你预期的示例图一致。

内容的提问来源于stack exchange,提问作者Iraponti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:46:14