基于OpenCV 的图像极坐标变换
- 目的
- Halcon算法实现
- OpenCV算法实现
- 原理
- 极坐标变换
- 极坐标反变换
- 原始图像->变换->反变换
- 代码
目的
极坐标变换的主要目的为将环形区域变换为矩形区域,从而便于字符识别等操作。最初接触极坐标变换为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)
原始图像:
极坐标变换结果:
反变换结果:
OpenCV算法实现
实现自:
原理
由Halcon的结果可以得到,整个图像就好像被展开一样,矩形图像的高 与 圆形图像的半径的相等,圆形图像的周长等于矩形图像的宽。从而构建由圆形图像到矩形图像的尺度变换关系,更详细的(x,y)对应)参见:
[极坐标对应关系详细表述]
图像引用自
极坐标变换
- 确定矩形展开图像的尺寸
// src为原始图像 src的宽度即为直径,展开图像的宽度即为 CV_PI * d; 高度为原始图像的一般,即为半径
Mat dst = Mat::zeros(Size((int) (src.cols * CV_PI) + 1, src.cols / 2 + 1), CV_8UC1);
- 确定尺寸变换的系数
即从原始图像变换到矩形图像的变换关系。
角度: 矩形图上每一点的位置对应原始图像上的角度,表示原始图像上的像素x坐标。
长度:矩形图上每一点的位置对应原始图像上的长度,表示原始图像上的像素y坐标。
double scale_r = src.cols / (dst.rows);
double scale_theta = 2 * CV_PI / dst.cols;
- 循环遍历矩形图dst中的每一点,求其对应的在原始图src中的像素值
- 插值算法(可以使用最近邻插值,或者使用双线性插值)
原理可参考:
插值算法插值算法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;
}
极坐标反变换
由一张方形图像转换到圆形图像,也就是这个效果:
也就是反向来一次,注意变换关系的约束。
原始图像->变换->反变换
代码
//
// 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})