关注

【android opencv学习笔记】Day 8: remap(像素位置重映射)

在这里插入图片描述

OpenCV remap 实现图像重映射特效

在图像处理中,图像重映射(Remapping) 是一种不修改像素值、只改变像素位置的核心技术,通过重新定义每个像素的坐标映射关系,既能实现波浪、翻转、扭曲等创意特效,也能修正镜头畸变、透视变形等工程问题。

本文将基于 OpenCV 的 cv::remap 函数,结合 Android Native (C++) 实现,讲解重映射的核心原理、波浪/翻转特效实现,并提供完整可运行的项目代码。


核心原理

图像重映射的本质是反向映射

  • 目标图像中每个像素点 (i,j),需要通过映射关系,找到它在原始图像中的对应位置 (srcX(i,j), srcY(i,j)),再把原始图像该位置的像素值赋值给目标点。
  • OpenCV 中,通过两个浮点型矩阵 srcXsrcY 来定义映射关系:
    • 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)

这是波浪效果的关键:

  1. 基准位置还是原本的行坐标 i
  2. 叠加 sin 正弦函数:正弦值会规律循环在 -1 ~ 1 之间
  3. 乘以系数 5:控制波浪起伏幅度(数值越大扭曲越强烈)
  4. 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

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--