Skip to content

Commit e1276d8

Browse files
committed
Parallel implementation for Radon transform
1 parent 80f1ca2 commit e1276d8

File tree

3 files changed

+118
-100
lines changed

3 files changed

+118
-100
lines changed

modules/ximgproc/include/opencv2/ximgproc/radon_transform.hpp

+37-18
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,43 @@
1010

1111
namespace cv { namespace ximgproc {
1212
/**
13-
* @brief Calculate Radon Transform of an image.
14-
* @param src The source (input) image.
15-
* @param dst The destination image, result of transformation.
16-
* @param theta Angle resolution of the transform in degrees.
17-
* @param start_angle Start angle of the transform in degrees.
18-
* @param end_angle End angle of the transform in degrees.
19-
* @param crop Crop the source image into a circle.
20-
* @param norm Normalize the output Mat to grayscale and convert type to CV_8U
21-
*
22-
* This function calculates the Radon Transform of a given image in any range.
23-
* See https://engineering.purdue.edu/~malcolm/pct/CTI_Ch03.pdf for detail.
24-
* If the input type is CV_8U, the output will be CV_32S.
25-
* If the input type is CV_32F or CV_64F, the output will be CV_64F
26-
* The output size will be num_of_integral x src_diagonal_length.
27-
* If crop is selected, the input image will be crop into square then circle,
28-
* and output size will be num_of_integral x min_edge.
29-
*
30-
*/
13+
* @brief Computes the Radon transform of a given 2D image.
14+
*
15+
* The Radon transform is often used in image processing, particularly in applications
16+
* like computed tomography, to analyze the structure of an image by projecting it along
17+
* different angles. This function calculates the Radon Transform over a specified range
18+
* of angles.
19+
*
20+
* The output type will vary depending on the input type:
21+
* - If the input type is CV_8U, the output will be CV_32S.
22+
* - If the input type is CV_32F or CV_64F, the output will be CV_64F.
23+
*
24+
* The size of the output matrix depends on whether cropping is applied:
25+
* - Without cropping, the output size will be `num_of_integral x src_diagonal_length`.
26+
* - With cropping (circular), the output size will be `num_of_integral x min_edge`,
27+
* where `min_edge` is the smaller dimension of the cropped square.
28+
*
29+
* See https://engineering.purdue.edu/~malcolm/pct/CTI_Ch03.pdf for more details.
30+
*
31+
* @param src The input image on which the Radon transform is to be applied.
32+
* Must be a 2D single-channel array (e.g., grayscale image).
33+
* @param dst The output array that will hold the result of the Radon transform.
34+
* The type of the output will depend on the input image type.
35+
* @param theta The angle increment in degrees for the projection (resolution of the transform).
36+
* Default is 1 degree.
37+
* @param start_angle The starting angle for the Radon transform in degrees.
38+
* Default is 0 degrees.
39+
* @param end_angle The ending angle for the Radon transform in degrees.
40+
* Default is 180 degrees. The difference between end_angle and start_angle must
41+
* be positive when multiplied by theta.
42+
* @param crop A flag indicating whether to crop the input image to a square or circular shape
43+
* before the transformation. If enabled, the image is first cropped to a square
44+
* (smallest dimension) and then transformed into a circle.
45+
* @param norm A flag indicating whether to normalize the output image to the range [0, 255] after
46+
* computation and convert the type to `CV_8U`. If normalization is not enabled,
47+
* the output will retain its original data range.
48+
*
49+
*/
3150
CV_EXPORTS_W void RadonTransform(InputArray src,
3251
OutputArray dst,
3352
double theta = 1,

modules/ximgproc/src/radon_transform.cpp

+65-63
Original file line numberDiff line numberDiff line change
@@ -5,77 +5,79 @@
55
#include "precomp.hpp"
66

77
namespace cv {namespace ximgproc {
8-
void RadonTransform(InputArray src,
9-
OutputArray dst,
10-
double theta,
11-
double start_angle,
12-
double end_angle,
13-
bool crop,
14-
bool norm)
15-
{
16-
CV_Assert(src.dims() == 2);
17-
CV_Assert(src.channels() == 1);
18-
CV_Assert((end_angle - start_angle) * theta > 0);
8+
void RadonTransform(InputArray src,
9+
OutputArray dst,
10+
double theta,
11+
double start_angle,
12+
double end_angle,
13+
bool crop,
14+
bool norm)
15+
{
16+
CV_Assert(src.dims() == 2);
17+
CV_Assert(src.channels() == 1);
18+
CV_Assert((end_angle - start_angle) * theta > 0);
1919

20-
Mat _srcMat = src.getMat();
20+
int col_num = cvRound((end_angle - start_angle) / theta);
21+
int row_num, out_mat_type;
22+
Point center;
23+
Mat srcMat, masked_src;
2124

22-
int _row_num, _col_num, _out_mat_type;
23-
_col_num = cvRound((end_angle - start_angle) / theta);
24-
transpose(_srcMat, _srcMat);
25-
Mat _masked_src;
26-
cv::Point _center;
25+
transpose(src, srcMat);
2726

28-
if (_srcMat.type() == CV_32FC1 || _srcMat.type() == CV_64FC1) {
29-
_out_mat_type = CV_64FC1;
30-
}
31-
else {
32-
_out_mat_type = CV_32SC1;
33-
}
27+
if (srcMat.type() == CV_32FC1 || srcMat.type() == CV_64FC1) {
28+
out_mat_type = CV_64FC1;
29+
}
30+
else {
31+
out_mat_type = CV_32SC1;
32+
}
3433

35-
if (crop) {
36-
// crop the source into square
37-
_row_num = min(_srcMat.rows, _srcMat.cols);
38-
cv::Rect _crop_ROI(
39-
_srcMat.cols / 2 - _row_num / 2,
40-
_srcMat.rows / 2 - _row_num / 2,
41-
_row_num, _row_num);
42-
_srcMat = _srcMat(_crop_ROI);
43-
// crop the source into circle
44-
Mat _mask(_srcMat.size(), CV_8UC1, Scalar(0));
45-
_center = Point(_srcMat.cols / 2, _srcMat.rows / 2);
46-
circle(_mask, _center, _srcMat.cols / 2, Scalar(255), FILLED);
47-
_srcMat.copyTo(_masked_src, _mask);
48-
}
49-
else {
50-
// avoid cropping corner when rotating
51-
_row_num = cvCeil(sqrt(_srcMat.rows * _srcMat.rows + _srcMat.cols * _srcMat.cols));
52-
_masked_src = Mat(Size(_row_num, _row_num), _srcMat.type(), Scalar(0));
53-
_center = Point(_masked_src.cols / 2, _masked_src.rows / 2);
54-
_srcMat.copyTo(_masked_src(Rect(
55-
(_row_num - _srcMat.cols) / 2,
56-
(_row_num - _srcMat.rows) / 2,
57-
_srcMat.cols, _srcMat.rows)));
58-
}
34+
if (crop) {
35+
// Crop the source into square
36+
row_num = min(srcMat.rows, srcMat.cols);
37+
Rect crop_ROI(
38+
srcMat.cols / 2 - row_num / 2,
39+
srcMat.rows / 2 - row_num / 2,
40+
row_num, row_num);
41+
srcMat = srcMat(crop_ROI);
5942

60-
double _t;
61-
Mat _rotated_src;
62-
Mat _radon(_row_num, _col_num, _out_mat_type);
43+
// Crop the source into circle
44+
Mat mask(srcMat.size(), CV_8UC1, Scalar(0));
45+
center = Point(srcMat.cols / 2, srcMat.rows / 2);
46+
circle(mask, center, srcMat.cols / 2, Scalar(255), FILLED);
47+
srcMat.copyTo(masked_src, mask);
48+
}
49+
else {
50+
// Avoid cropping corner when rotating
51+
row_num = cvCeil(sqrt(srcMat.rows * srcMat.rows + srcMat.cols * srcMat.cols));
52+
masked_src = Mat(Size(row_num, row_num), srcMat.type(), Scalar(0));
53+
center = Point(masked_src.cols / 2, masked_src.rows / 2);
54+
srcMat.copyTo(masked_src(Rect(
55+
(row_num - srcMat.cols) / 2,
56+
(row_num - srcMat.rows) / 2,
57+
srcMat.cols, srcMat.rows)));
58+
}
6359

64-
for (int _col = 0; _col < _col_num; _col++) {
65-
// rotate the source by _t
66-
_t = (start_angle + _col * theta);
67-
cv::Mat _r_matrix = cv::getRotationMatrix2D(_center, _t, 1);
68-
cv::warpAffine(_masked_src, _rotated_src, _r_matrix, _masked_src.size());
69-
Mat _col_mat = _radon.col(_col);
70-
// make projection
71-
cv::reduce(_rotated_src, _col_mat, 1, REDUCE_SUM, _out_mat_type);
72-
}
60+
dst.create(row_num, col_num, out_mat_type);
61+
Mat radon = dst.getMat();
62+
63+
// Define the parallel loop as a lambda function
64+
parallel_for_(Range(0, col_num), [&](const Range& range) {
65+
for (int col = range.start; col < range.end; col++) {
66+
// Rotate the source by t
67+
double t = (start_angle + col * theta);
68+
Mat r_matrix = getRotationMatrix2D(center, t, 1);
69+
70+
Mat rotated_src;
71+
warpAffine(masked_src, rotated_src, r_matrix, masked_src.size());
7372

74-
if (norm) {
75-
normalize(_radon, _radon, 0, 255, NORM_MINMAX, CV_8UC1);
73+
Mat col_mat = radon.col(col);
74+
// Make projection
75+
reduce(rotated_src, col_mat, 1, REDUCE_SUM, out_mat_type);
7676
}
77+
});
7778

78-
_radon.copyTo(dst);
79-
return;
79+
if (norm) {
80+
normalize(radon, dst.getMatRef(), 0, 255, NORM_MINMAX, CV_8UC1);
8081
}
82+
}
8183
} }

modules/ximgproc/test/test_radon_transform.cpp

+16-19
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,36 @@ namespace opencv_test {namespace {
99
TEST(RadonTransformTest, output_size)
1010
{
1111
Mat src(Size(256, 256), CV_8U, Scalar(0));
12-
circle(src, Point(128, 128), 64, Scalar(255), FILLED);
1312
Mat radon;
14-
cv::ximgproc::RadonTransform(src, radon);
1513

14+
ximgproc::RadonTransform(src, radon);
1615
EXPECT_EQ(363, radon.rows);
1716
EXPECT_EQ(180, radon.cols);
1817

19-
cv::ximgproc::RadonTransform(src, radon, 1, 0, 180, true);
20-
18+
ximgproc::RadonTransform(src, radon, 1, 0, 180, true);
2119
EXPECT_EQ(256, radon.rows);
2220
EXPECT_EQ(180, radon.cols);
2321
}
2422

2523
TEST(RadonTransformTest, output_type)
2624
{
2725
Mat src_int(Size(256, 256), CV_8U, Scalar(0));
28-
circle(src_int, Point(128, 128), 64, Scalar(255), FILLED);
26+
Mat src_float(Size(256, 256), CV_32FC1, Scalar(0));
27+
Mat src_double(Size(256, 256), CV_64FC1, Scalar(0));
2928
Mat radon, radon_norm;
30-
cv::ximgproc::RadonTransform(src_int, radon);
31-
cv::ximgproc::RadonTransform(src_int, radon_norm, 1, 0, 180, false, true);
3229

30+
ximgproc::RadonTransform(src_int, radon);
31+
ximgproc::RadonTransform(src_int, radon_norm, 1, 0, 180, false, true);
3332
EXPECT_EQ(CV_32SC1, radon.type());
3433
EXPECT_EQ(CV_8U, radon_norm.type());
3534

36-
Mat src_float(Size(256, 256), CV_32FC1, Scalar(0));
37-
Mat src_double(Size(256, 256), CV_32FC1, Scalar(0));
38-
cv::ximgproc::RadonTransform(src_float, radon);
39-
cv::ximgproc::RadonTransform(src_float, radon_norm, 1, 0, 180, false, true);
35+
ximgproc::RadonTransform(src_float, radon);
36+
ximgproc::RadonTransform(src_float, radon_norm, 1, 0, 180, false, true);
4037
EXPECT_EQ(CV_64FC1, radon.type());
4138
EXPECT_EQ(CV_8U, radon_norm.type());
42-
cv::ximgproc::RadonTransform(src_double, radon);
39+
40+
ximgproc::RadonTransform(src_double, radon);
41+
ximgproc::RadonTransform(src_double, radon_norm, 1, 0, 180, false, true);
4342
EXPECT_EQ(CV_64FC1, radon.type());
4443
EXPECT_EQ(CV_8U, radon_norm.type());
4544
}
@@ -49,31 +48,29 @@ TEST(RadonTransformTest, accuracy_by_pixel)
4948
Mat src(Size(256, 256), CV_8U, Scalar(0));
5049
circle(src, Point(128, 128), 64, Scalar(255), FILLED);
5150
Mat radon;
52-
cv::ximgproc::RadonTransform(src, radon);
5351

52+
ximgproc::RadonTransform(src, radon);
5453
ASSERT_EQ(CV_32SC1, radon.type());
55-
5654
EXPECT_EQ(0, radon.at<int>(0, 0));
57-
5855
EXPECT_LT(18000, radon.at<int>(128, 128));
5956
EXPECT_GT(19000, radon.at<int>(128, 128));
6057
}
6158

6259
TEST(RadonTransformTest, accuracy_uchar)
6360
{
6461
Mat src(Size(10, 10), CV_8UC1, Scalar(1));
65-
cv::Mat radon;
66-
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false);
62+
Mat radon;
6763

64+
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false);
6865
EXPECT_EQ(100, sum(radon.col(0))[0]);
6966
}
7067

7168
TEST(RadonTransformTest, accuracy_float)
7269
{
7370
Mat src(Size(10, 10), CV_32FC1, Scalar(1.1));
74-
cv::Mat radon;
75-
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false);
71+
Mat radon;
7672

73+
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false);
7774
EXPECT_LT(109, sum(radon.col(0))[0]);
7875
EXPECT_GT(111, sum(radon.col(0))[0]);
7976
}

0 commit comments

Comments
 (0)