
OpenCV remap 实现图像重映射特效
在图像处理中,图像重映射(Remapping) 是一种不修改像素值、只改变像素位置的核心技术,通过重新定义每个像素的坐标映射关系,既能实现波浪、翻转、扭曲等创意特效,也能修正镜头畸变、透视变形等工程问题。
本文将基于 OpenCV 的 cv::remap 函数,结合 Android Native (C++) 实现,讲解重映射的核心原理、波浪/翻转特效实现,并提供完整可运行的项目代码。
核心原理
图像重映射的本质是反向映射:
- 目标图像中每个像素点
(i,j),需要通过映射关系,找到它在原始图像中的对应位置(srcX(i,j), srcY(i,j)),再把原始图像该位置的像素值赋值给目标点。 - OpenCV 中,通过两个浮点型矩阵
srcX和srcY来定义映射关系:srcX.at<float>(i,j):目标点(i,j)在原图中的 X 坐标srcY.at<float>(i,j):目标点(i,j)在原图中的 Y 坐标
- 浮点坐标支持亚像素级映射,搭配插值算法(如双线性插值
INTER_LINEAR),实现平滑的扭曲效果。
两种典型重映射特效实现
1. 波浪特效(正弦曲线扭曲)
通过 sin 函数动态修改像素的 Y 坐标,让图像呈现水平波浪效果:
void wave(const cv::Mat &image, cv::Mat &result) {
// 创建映射参数矩阵(与原图尺寸一致,浮点型)
cv::Mat srcX(image.rows, image.cols, CV_32F);
cv::Mat srcY(image.rows, image.cols, CV_32F);
// 定义映射关系
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
// X 坐标保持不变,图像不发生水平偏移
srcX.at<float>(i, j) = j;
// Y 坐标根据正弦曲线波动,形成波浪效果
srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);
}
}
// 应用映射参数,生成波浪效果图像
cv::remap(image, result, srcX, srcY, cv::INTER_LINEAR);
}
2. 水平翻转特效
通过反转 X 坐标实现图像水平镜像:
void flipHorizontal(const cv::Mat &image, cv::Mat &result) {
cv::Mat srcX(image.rows, image.cols, CV_32F);
cv::Mat srcY(image.rows, image.cols, CV_32F);
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
// X 坐标反转,实现水平翻转
srcX.at<float>(i, j) = image.cols - j - 1;
// Y 坐标保持不变
srcY.at<float>(i, j) = i;
}
}
cv::remap(image, result, srcX, srcY, cv::INTER_LINEAR);
}
Android Native 完整项目实现
1. 布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<!-- 第一行:原图 -->
<ImageView
android:id="@+id/iv_src"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"/>
<!-- 第二行:波浪 + 翻转结果 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_wave"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/iv_flip"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"/>
</LinearLayout>
</LinearLayout>
2. Native 层核心代码 native-lib.cpp
包含格式互转、波浪特效、水平翻转的完整实现:
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
using namespace cv;
// Bitmap → Mat
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
Mat mat(info.height, info.width, CV_8UC4, pixels);
Mat bgr;
cvtColor(mat, bgr, COLOR_RGBA2BGR);
AndroidBitmap_unlockPixels(env, bitmap);
return bgr;
}
// Mat → Bitmap
void matToBitmap(JNIEnv *env, const Mat &mat, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
Mat rgba;
cvtColor(mat, rgba, COLOR_BGR2RGBA);
memcpy(pixels, rgba.data, info.height * info.width * 4);
AndroidBitmap_unlockPixels(env, bitmap);
}
// 波浪特效
extern "C" JNIEXPORT void JNICALL
Java_com_nicoli_remapdemo_MainActivity_waveEffect(
JNIEnv *env, jobject, jobject bmp, jobject out) {
Mat image = bitmapToMat(env, bmp);
Mat result;
Mat srcX(image.rows, image.cols, CV_32F);
Mat srcY(image.rows, image.cols, CV_32F);
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
srcX.at<float>(i, j) = j;
srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);
}
}
remap(image, result, srcX, srcY, INTER_LINEAR);
matToBitmap(env, result, out);
}
// 水平翻转特效
extern "C" JNIEXPORT void JNICALL
Java_com_nicoli_remapdemo_MainActivity_flipHorizontalEffect(
JNIEnv *env, jobject, jobject bmp, jobject out) {
Mat image = bitmapToMat(env, bmp);
Mat result;
Mat srcX(image.rows, image.cols, CV_32F);
Mat srcY(image.rows, image.cols, CV_32F);
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
srcX.at<float>(i, j) = image.cols - j - 1;
srcY.at<float>(i, j) = i;
}
}
remap(image, result, srcX, srcY, INTER_LINEAR);
matToBitmap(env, result, out);
}
3. Kotlin 层代码 MainActivity.kt
package com.nicoli.remapdemo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
external fun waveEffect(bmp: Bitmap, out: Bitmap)
external fun flipHorizontalEffect(bmp: Bitmap, out: Bitmap)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 加载原图
val src = BitmapFactory.decodeResource(resources, R.drawable.puppy)
findViewById<ImageView>(R.id.iv_src).setImageBitmap(src)
// 创建结果位图
val w = src.width
val h = src.height
val outWave = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val outFlip = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
// 调用 Native 重映射特效
waveEffect(src, outWave)
flipHorizontalEffect(src, outFlip)
// 显示结果
findViewById<ImageView>(R.id.iv_wave).setImageBitmap(outWave)
findViewById<ImageView>(R.id.iv_flip).setImageBitmap(outFlip)
}
}

关键细节与核心概念
1. 反向映射的意义
remap 采用反向映射(目标 → 原图),而非正向映射(原图 → 目标),原因是:
- 正向映射会出现目标像素未被覆盖的空洞,需要额外处理
- 反向映射保证每个目标像素都能找到对应源像素,避免空洞问题
2. 插值算法的作用
remap 最后一个参数指定插值方式,浮点坐标映射时,需要通过插值计算亚像素位置的像素值:
INTER_LINEAR:双线性插值,平滑无锯齿,是最常用的选项INTER_NEAREST:最近邻插值,速度快但边缘易出现锯齿INTER_CUBIC:三次插值,效果更平滑但计算量更大
3. 工程与创意应用场景
- 镜头畸变校正:通过预定义的畸变映射矩阵,修正鱼眼、广角镜头的桶形/枕形畸变
- 图像特效:波浪、水波纹、扭曲、哈哈镜效果,短视频/相机滤镜常用
- 图像拼接:全景图像拼接时,通过重映射实现透视对齐
- 证件照处理:实现照片水平翻转、镜像特效
波浪效果代码详细解析
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
srcX.at<float>(i, j) = j;
srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);
}
}
remap(image, result, srcX, srcY, INTER_LINEAR);
1. 坐标基础认知
i:代表行,对应图像纵向 Y 轴j:代表列,对应图像横向 X 轴- 双层循环 = 遍历图片所有像素点
2. srcX.at(i, j) = j
固定 X 坐标不变:
代表所有像素左右位置保持原样,图片不会横向拉伸、偏移。
3. srcY.at(i, j) = i + 5 * sin(j / 10.0)
这是波浪效果的关键:
- 基准位置还是原本的行坐标
i - 叠加
sin正弦函数:正弦值会规律循环在-1 ~ 1之间 - 乘以系数
5:控制波浪起伏幅度(数值越大扭曲越强烈) j / 10.0:控制波浪疏密程度,数值越小波纹越密集
简单理解:
图片越往右的列,Y 坐标就会按照正弦规律不断上下偏移,
整体画面就形成连续、顺滑的横向波浪扭曲效果。
4. remap 执行映射
remap(image, result, srcX, srcY, INTER_LINEAR);
读取我们定义好的整张坐标映射表,
按照新坐标规则,逐像素从原图采样,生成扭曲后的新图片。
INTER_LINEAR 双线性插值,保证扭曲边缘平滑不锯齿。
水平翻转实现原理
srcX.at<float>(i, j) = image.cols - j - 1;
srcY.at<float>(i, j) = i;
- Y 坐标不变,上下位置不变
- X 坐标左右颠倒,实现镜像水平翻转
总结
OpenCV 的 cv::remap 是图像几何变换的核心函数,通过自定义映射矩阵,既能实现创意特效,也能解决工程中的畸变校正、图像对齐问题。

转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/u012739527/article/details/160529955



