图像极坐标变换与反变换(Halcon + OpenCV)

12 篇文章 0 订阅

目的

极坐标变换的主要目的为将环形区域变换为矩形区域,从而便于字符识别等操作。最初接触极坐标变换为Halcon中的例程(检测啤酒瓶瓶口缺陷* inspect_bottle_mouth.hdev*)。
例程运行结果
本项目就是基于OpenCV将图像用极坐标表示,实现圆环区域转换为矩形区域。(不调用函数)

Halcon算法实现

针对成熟的机器视觉算法库,Halcon仅需要一行算子就可以实现图像的极坐标变换与反变换。

* 极坐标变换
read_image (Image, 'C:/Users/zhangzelu/Pictures/41467_2020_15522_Fig1_HTML.png') 
get_image_size (Image, Width, Height)
polar_trans_image_ext (Image, PolarTransImage, Width/2, Height/2, 0, -6.28319, 0, Height/2, 3.14*Width, Height/2, 'nearest_neighbor')
dev_close_window ()
dev_open_window_fit_image (PolarTransImage, 0, 0, -1, -1, WindowHandle)
dev_display (PolarTransImage)

* 反变换
polar_trans_image_inv (PolarTransImage, XYTransImage, Width/2, Height/2, 0, -6.28319, 0,  Height/2, Height, Height, 'nearest_neighbor')
dev_close_window ()
dev_open_window_fit_image (XYTransImage, 0, 0, -1, -1, WindowHandle)
dev_display (XYTransImage)

原始图像:
原始图像

极坐标变换结果:
Halcon_极坐标变换
反变换结果:
反变换结果

OpenCV算法实现

实现自:

https://blog.csdn.net/guduruyu/article/details/65436931

原理

由Halcon的结果可以得到,整个图像就好像被展开一样,矩形图像的高 与 圆形图像的半径的相等,圆形图像的周长等于矩形图像的宽。从而构建由圆形图像到矩形图像的尺度变换关系,更详细的(x,y)对应 ( p , θ (p,\theta (pθ)参见:

https://blog.csdn.net/qq_32939413/article/details/104092627[极坐标对应关系详细表述]

引用自博文
图像引用自

https://blog.csdn.net/qq_32939413/article/details/104092627

极坐标变换

OpenCV_极坐标变换

  1. 确定矩形展开图像的尺寸
// src为原始图像 src的宽度即为直径,展开图像的宽度即为 CV_PI * d; 高度为原始图像的一般,即为半径
Mat dst = Mat::zeros(Size((int) (src.cols * CV_PI) + 1, src.cols / 2 + 1), CV_8UC1);
  1. 确定尺寸变换的系数
    即从原始图像变换到矩形图像的变换关系。
    角度: 矩形图上每一点的位置对应原始图像上的角度, x x x表示原始图像上的像素x坐标。
    2 ⋅ Π d s t . c o l s = θ x ( d s t ) \frac{2 \cdot \Pi}{dst.cols} = \frac{ \theta }{ x(dst)} dst.cols2Π=x(dst)θ
    长度:矩形图上每一点的位置对应原始图像上的长度, y y y表示原始图像上的像素y坐标。
    s r c . c o l s d s t . r o w s = p y ( d s t ) \frac{src.cols}{dst.rows} = \frac{ p}{y(dst)} dst.rowssrc.cols=y(dst)p
    double scale_r = src.cols / (dst.rows);
    double scale_theta = 2 * CV_PI / dst.cols;
  1. 循环遍历矩形图dst中的每一点,求其对应的在原始图src中的像素值
    示意图
  2. 插值算法(可以使用最近邻插值,或者使用双线性插值)
    原理可参考:
    插值算法
    插值算法2 本篇的主要灵感来源
uchar getPixel(const Mat &src, double X, int X_up, int X_down, double Y, int Y_up, int Y_down) {
    // 插值算法
    // X 为水平方向坐标 X_up、X_down分别为向上向下取整后的值 Y同理
    double inter_val = 0;
    if (X_up == X_down && Y_up == Y_down) {
        inter_val = saturate_cast<uchar>(src.at<uchar>(Y_up, X_up));
    } else if (X_up == X_down) {
        inter_val = saturate_cast<uchar>((Y_up - Y) * src.at<uchar>(Y_up, X_up) +
                                         (Y - Y_down) * src.at<uchar>(Y_down, X_up));
    } else if (Y_up == Y_down) {
        inter_val = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_up, X_up) +
                                         (X - X_down) * src.at<uchar>(Y_up, X_down));
    } else {
        double Y_tmp = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_down, X_up) +
                                        (X - X_down) * src.at<uchar>(Y_down, X_down));
        double X_tmp = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_up, X_up) +
                                        (X - X_down) * src.at<uchar>(Y_up, X_down));
        inter_val = (Y_up - Y) * X_tmp + (Y - Y_down) * Y_tmp;
    }

    return (uchar) inter_val;
}
极坐标反变换

由一张方形图像转换到圆形图像,也就是这个效果:
也就是反向来一次,注意变换关系的约束。
OpenCV极坐标变换

原始图像->变换->反变换

结果图像

代码

//
// Created by zzl on 2020/12/20.
//

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

// 矩形图像转圆形
Mat Rectangle_to_Circle(const Mat &src, int Width);

Mat Circle_to_Rectangle(const Mat &src);

uchar getPixel(const Mat &src, double X, int X_up, int X_down, double Y, int Y_up, int Y_down);

int main(int argc, char **argv) {
    // 读入图像
    Mat src, dst;
    src = imread("/home/zzl/Blog/CoordinateTrans/Data/testimage4.jpg", 0);

    namedWindow("InputImages", WINDOW_NORMAL);
    imshow("InputImages", src);

    dst = Rectangle_to_Circle(src, 2 * src.rows);
    namedWindow("OutputImages", WINDOW_NORMAL);
    imshow("OutputImages", dst);

    Mat dst2;
    dst2 = Circle_to_Rectangle(dst);
    namedWindow("OutputImages2", WINDOW_FREERATIO);
    imshow("OutputImages2", dst2);



    cout << "Hello World" << endl;
    waitKey();
    return 0;
}

Mat Rectangle_to_Circle(const Mat &src, int Width) {

    int src_height = src.rows;
    int src_width = src.cols;

    Size dstSize = Size(2 * src_height, 2 * src_height);
    Mat dst = Mat::zeros(dstSize, CV_8UC1);
    // 极坐标变换
    double scale_r = 2 * src_height / (dstSize.width);
    double scale_theta = src_width / CV_2PI;
    Mat tmp = Mat::zeros(dst.size(), CV_64FC1);
    for (int i = 0; i < dstSize.height; ++i) {
        for (int j = 0; j < dstSize.width; ++j) {
            // 计算距离
            Point2d center(dstSize.width / 2, dstSize.width / 2);
            double distance = sqrt(pow(i - center.y, 2) + pow(j - center.x, 2));
//            tmp.at<double>(j, i) = distance;
            if (distance < dstSize.width / 2) {
                // 处于边界内部的点,可以提取像素
                // 坐标变换求对应方图上的点的坐标
                double Rec_Y = distance * scale_r; //Y 方向坐标
                if (Rec_Y < 0) {
                    Rec_Y = 0;
                }
                if (Rec_Y > dstSize.width / 2) {
                    Rec_Y = dstSize.width / 2;
                }
                double line_theta = atan2(i - center.y, j - center.x);
                if (line_theta < 0) {
                    line_theta += CV_2PI;
                }
                if (line_theta < 0) {
                    cout << "仍然小于0" << endl;
                }
                double Rec_X = line_theta * scale_theta;
                dst.at<uchar>(i, j) = src.at<uchar>((int) Rec_Y, (int) Rec_X);
            }
        }
    }
    // ---- 显示图像边界距离
//    normalize(tmp, tmp, 0, 1, NORM_MINMAX);
//    Mat display;
//    tmp.convertTo(display, CV_8UC1, 255.0);
//
//    namedWindow("Distance", WINDOW_NORMAL);
//    imshow("Distance", display);
//    waitKey();
//    circle(display, Point2d(Width / 2, Width / 2), Width / 2, 0, 10, LINE_8);
//    imshow("Distance", display);
//    waitKey();


    return dst;
}

Mat Circle_to_Rectangle(const Mat &src) {
    // 变换不同的图像大小有不同的效果
    Mat dst = Mat::zeros(Size((int) (src.cols * CV_PI) + 1, src.cols / 2 + 1), CV_8UC1);
//    Mat dst = Mat::zeros(Size(src.cols / 2 + 1, src.cols / 2 + 1), CV_8UC1);
    double scale_r = src.cols / (dst.rows);
    double scale_theta = 2 * CV_PI / dst.cols;
    for (int i = 0; i < dst.cols; ++i) {
        double theta = i * scale_theta;
        double sinTheta = sin(theta);
        double cosTheta = cos(theta);
        for (int j = 0; j < dst.rows; ++j) {
            double p = j * scale_r;
            double X = (src.rows / 2 + cosTheta * p);
            double Y = (src.cols / 2 + sinTheta * p);

            int X_up = ceil(X);
            int X_down = floor(X);
            int Y_up = ceil(Y);
            int Y_down = floor(Y);

            if (X > src.cols) {
                X = src.cols;
            }
            if (X < 0) {
                X = 0;
            }
            if (Y > src.rows) {
                Y = src.rows;
            }
            if (Y < 0) {
                Y = 0;
            }
            // 若使用插值算法需要取消注释
//            uchar tmp_Pixel = getPixel(src, X, X_up, X_down, Y, Y_up, Y_down);
//            dst.at<uchar>(j, i) = tmp_Pixel;
            dst.at<uchar>(j, i) = src.at<uchar>(Y, X); // 最近邻算法
        }

    }
    return dst;
}

uchar getPixel(const Mat &src, double X, int X_up, int X_down, double Y, int Y_up, int Y_down) {
    // 插值算法
    // X 为水平方向坐标 X_up、X_down分别为向上向下取整后的值 Y同理
    double inter_val = 0;
    if (X_up == X_down && Y_up == Y_down) {
        inter_val = saturate_cast<uchar>(src.at<uchar>(Y_up, X_up));
    } else if (X_up == X_down) {
        inter_val = saturate_cast<uchar>((Y_up - Y) * src.at<uchar>(Y_up, X_up) +
                                         (Y - Y_down) * src.at<uchar>(Y_down, X_up));
    } else if (Y_up == Y_down) {
        inter_val = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_up, X_up) +
                                         (X - X_down) * src.at<uchar>(Y_up, X_down));
    } else {
        double Y_tmp = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_down, X_up) +
                                        (X - X_down) * src.at<uchar>(Y_down, X_down));
        double X_tmp = saturate_cast<uchar>((X_up - X) * src.at<uchar>(Y_up, X_up) +
                                        (X - X_down) * src.at<uchar>(Y_up, X_down));
        inter_val = (Y_up - Y) * X_tmp + (Y - Y_down) * Y_tmp;
    }

    return (uchar) inter_val;
}



# CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(CoordinateTrans)

set(CMAKE_CXX_STANDARD 14)
find_package(OpenCV 4 REQUIRED)
message(STATUS "OpenCV library status:")
message(STATUS "   OpenCV Version: ${OpenCV_VERSION}" )
include_directories(${OpenCV_INCLUDES})

add_executable(CoordinateTrans main.cpp)
target_link_libraries(CoordinateTrans ${OpenCV_LIBS})
  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值