@ -1,2 +1,31 @@ |
||||
set(the_description "ArUco Marker Detection") |
||||
ocv_define_module(aruco opencv_core opencv_imgproc opencv_3d opencv_calib WRAP python java objc js) |
||||
ocv_include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
||||
|
||||
ocv_add_testdata(samples/ contrib/aruco |
||||
FILES_MATCHING PATTERN "*yml" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/aruco_detection/images/ contrib/aruco |
||||
FILES_MATCHING PATTERN "singlemarkersoriginal.jpg" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/aruco_board_detection/images/ contrib/aruco |
||||
FILES_MATCHING PATTERN "gboriginal.png" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/charuco_detection/images/ contrib/aruco |
||||
FILES_MATCHING PATTERN "choriginal.jpg" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/charuco_detection/images/ contrib/aruco |
||||
FILES_MATCHING PATTERN "chocclusion_original.jpg" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/charuco_diamond_detection/images/ contrib/aruco |
||||
FILES_MATCHING PATTERN "diamondmarkers.png" |
||||
) |
||||
|
||||
ocv_add_testdata(tutorials/aruco_calibration/images/ contrib/aruco |
||||
FILES_MATCHING REGEX "img_[0-9]+.jpg" |
||||
) |
||||
|
@ -0,0 +1,48 @@ |
||||
#include <opencv2/highgui.hpp> |
||||
#include <opencv2/aruco.hpp> |
||||
#include <opencv2/calib3d.hpp> |
||||
#include <ctime> |
||||
|
||||
namespace { |
||||
inline static bool readCameraParameters(std::string filename, cv::Mat &camMatrix, cv::Mat &distCoeffs) { |
||||
cv::FileStorage fs(filename, cv::FileStorage::READ); |
||||
if (!fs.isOpened()) |
||||
return false; |
||||
fs["camera_matrix"] >> camMatrix; |
||||
fs["distortion_coefficients"] >> distCoeffs; |
||||
return true; |
||||
} |
||||
|
||||
inline static bool saveCameraParams(const std::string &filename, cv::Size imageSize, float aspectRatio, int flags, |
||||
const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, double totalAvgErr) { |
||||
cv::FileStorage fs(filename, cv::FileStorage::WRITE); |
||||
if (!fs.isOpened()) |
||||
return false; |
||||
|
||||
time_t tt; |
||||
time(&tt); |
||||
struct tm *t2 = localtime(&tt); |
||||
char buf[1024]; |
||||
strftime(buf, sizeof(buf) - 1, "%c", t2); |
||||
|
||||
fs << "calibration_time" << buf; |
||||
fs << "image_width" << imageSize.width; |
||||
fs << "image_height" << imageSize.height; |
||||
|
||||
if (flags & cv::CALIB_FIX_ASPECT_RATIO) fs << "aspectRatio" << aspectRatio; |
||||
|
||||
if (flags != 0) { |
||||
sprintf(buf, "flags: %s%s%s%s", |
||||
flags & cv::CALIB_USE_INTRINSIC_GUESS ? "+use_intrinsic_guess" : "", |
||||
flags & cv::CALIB_FIX_ASPECT_RATIO ? "+fix_aspectRatio" : "", |
||||
flags & cv::CALIB_FIX_PRINCIPAL_POINT ? "+fix_principal_point" : "", |
||||
flags & cv::CALIB_ZERO_TANGENT_DIST ? "+zero_tangent_dist" : ""); |
||||
} |
||||
fs << "flags" << flags; |
||||
fs << "camera_matrix" << cameraMatrix; |
||||
fs << "distortion_coefficients" << distCoeffs; |
||||
fs << "avg_reprojection_error" << totalAvgErr; |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
%YAML:1.0 |
||||
--- |
||||
calibration_time: "Wed 08 Dec 2021 05:13:09 PM MSK" |
||||
image_width: 640 |
||||
image_height: 480 |
||||
flags: 0 |
||||
camera_matrix: !!opencv-matrix |
||||
rows: 3 |
||||
cols: 3 |
||||
dt: d |
||||
data: [ 4.5251072219637672e+02, 0., 3.1770297317353277e+02, 0., |
||||
4.5676707935146891e+02, 2.7775155919135995e+02, 0., 0., 1. ] |
||||
distortion_coefficients: !!opencv-matrix |
||||
rows: 1 |
||||
cols: 5 |
||||
dt: d |
||||
data: [ 1.2136925618707872e-01, -1.0854664722560681e+00, |
||||
1.1786843796668460e-04, -4.6240686046485508e-04, |
||||
2.9542589406810080e+00 ] |
||||
avg_reprojection_error: 1.8234905535936044e-01 |
||||
info: "The camera calibration parameters were obtained by img_00.jpg-img_03.jpg from aruco/tutorials/aruco_calibration/images" |
@ -0,0 +1,14 @@ |
||||
%YAML:1.0 |
||||
camera_matrix: !!opencv-matrix |
||||
rows: 3 |
||||
cols: 3 |
||||
dt: d |
||||
data: [ 628.158, 0., 324.099, |
||||
0., 628.156, 260.908, |
||||
0., 0., 1. ] |
||||
distortion_coefficients: !!opencv-matrix |
||||
rows: 5 |
||||
cols: 1 |
||||
dt: d |
||||
data: [ 0.0995485, -0.206384, |
||||
0.00754589, 0.00336531, 0 ] |
@ -0,0 +1,38 @@ |
||||
%YAML:1.0 |
||||
nmarkers: 35 |
||||
markersize: 6 |
||||
marker_0: "101011111011111001001001101100000000" |
||||
marker_1: "000000000010011001010011111010111000" |
||||
marker_2: "011001100000001010000101111101001101" |
||||
marker_3: "001000111111000111011001110000011111" |
||||
marker_4: "100110110100101111000000111101110011" |
||||
marker_5: "010101101110111000111010111100010111" |
||||
marker_6: "101001000110011110101001010100110100" |
||||
marker_7: "011010100100110000011101110110100010" |
||||
marker_8: "111110001000101000110001010010111101" |
||||
marker_9: "011101101100110111001100100001010100" |
||||
marker_10: "100001100001010001110001011000000111" |
||||
marker_11: "110010010010011100101111111000001111" |
||||
marker_12: "110101001001010110011111010110001101" |
||||
marker_13: "001111000001000100010001101001010001" |
||||
marker_14: "000000010010101010111110110011010011" |
||||
marker_15: "110001110111100101110011111100111010" |
||||
marker_16: "101011001110001010110011111011001110" |
||||
marker_17: "101110111101110100101101011001010111" |
||||
marker_18: "000100111000111101010011010101000101" |
||||
marker_19: "001110001110001101100101110100000011" |
||||
marker_20: "100101101100010110110110110001100011" |
||||
marker_21: "010110001001011010000100111000110110" |
||||
marker_22: "001000000000100100000000010100010010" |
||||
marker_23: "101001110010100110000111111010010000" |
||||
marker_24: "111001101010001100011010010001011100" |
||||
marker_25: "101000010001010000110100111101101001" |
||||
marker_26: "101010000001010011001010110110000001" |
||||
marker_27: "100101001000010101001000111101111110" |
||||
marker_28: "010010100110010011110001110101011100" |
||||
marker_29: "011001000101100001101111010001001111" |
||||
marker_30: "000111011100011110001101111011011001" |
||||
marker_31: "010100001011000100111101110001101010" |
||||
marker_32: "100101101001101010111111101101110100" |
||||
marker_33: "101101001010111000000100110111010101" |
||||
marker_34: "011111010000111011111110110101100101" |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 385 KiB |
After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 387 KiB |
After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 404 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 348 KiB |
@ -1,3 +1,7 @@ |
||||
set(the_description "RGBD algorithms") |
||||
|
||||
ocv_define_module(rgbd opencv_core opencv_3d opencv_imgproc OPTIONAL opencv_viz WRAP python) |
||||
|
||||
if(HAVE_OPENGL) |
||||
ocv_target_link_libraries(${the_module} PRIVATE "${OPENGL_LIBRARIES}") |
||||
endif() |
||||
|
@ -0,0 +1,40 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef __OPENCV_RADON_TRANSFORM_HPP__ |
||||
#define __OPENCV_RADON_TRANSFORM_HPP__ |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
|
||||
namespace cv { namespace ximgproc { |
||||
/**
|
||||
* @brief Calculate Radon Transform of an image. |
||||
* @param src The source (input) image. |
||||
* @param dst The destination image, result of transformation. |
||||
* @param theta Angle resolution of the transform in degrees. |
||||
* @param start_angle Start angle of the transform in degrees. |
||||
* @param end_angle End angle of the transform in degrees. |
||||
* @param crop Crop the source image into a circle. |
||||
* @param norm Normalize the output Mat to grayscale and convert type to CV_8U |
||||
* |
||||
* This function calculates the Radon Transform of a given image in any range. |
||||
* See https://engineering.purdue.edu/~malcolm/pct/CTI_Ch03.pdf for detail.
|
||||
* If the input type is CV_8U, the output will be CV_32S. |
||||
* If the input type is CV_32F or CV_64F, the output will be CV_64F |
||||
* The output size will be num_of_integral x src_diagonal_length. |
||||
* If crop is selected, the input image will be crop into square then circle, |
||||
* and output size will be num_of_integral x min_edge. |
||||
* |
||||
*/ |
||||
CV_EXPORTS_W void RadonTransform(InputArray src, |
||||
OutputArray dst, |
||||
double theta = 1, |
||||
double start_angle = 0, |
||||
double end_angle = 180, |
||||
bool crop = false, |
||||
bool norm = false); |
||||
} } |
||||
|
||||
#endif |
@ -0,0 +1,83 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com)
|
||||
|
||||
#ifndef __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ |
||||
#define __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ |
||||
|
||||
#include <opencv2/core.hpp> |
||||
|
||||
namespace cv { namespace ximgproc { |
||||
|
||||
/** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels
|
||||
algorithm by Loke SC, et al. @cite loke2021accelerated for original paper. |
||||
|
||||
The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than |
||||
existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s |
||||
with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and |
||||
more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic |
||||
when the number of processing threads is fixed, and requires the source image to be in Lab colour format. |
||||
*/ |
||||
class CV_EXPORTS_W ScanSegment : public Algorithm |
||||
{ |
||||
public: |
||||
virtual ~ScanSegment(); |
||||
|
||||
/** @brief Returns the actual superpixel segmentation from the last image processed using iterate.
|
||||
|
||||
Returns zero if no image has been processed. |
||||
*/ |
||||
CV_WRAP virtual int getNumberOfSuperpixels() = 0; |
||||
|
||||
/** @brief Calculates the superpixel segmentation on a given image with the initialized
|
||||
parameters in the ScanSegment object. |
||||
|
||||
This function can be called again for other images without the need of initializing the algorithm with createScanSegment(). |
||||
This save the computational cost of allocating memory for all the structures of the algorithm. |
||||
|
||||
@param img Input image. Supported format: CV_8UC3. Image size must match with the initialized |
||||
image size with the function createScanSegment(). It MUST be in Lab color space. |
||||
*/ |
||||
CV_WRAP virtual void iterate(InputArray img) = 0; |
||||
|
||||
/** @brief Returns the segmentation labeling of the image.
|
||||
|
||||
Each label represents a superpixel, and each pixel is assigned to one superpixel label. |
||||
|
||||
@param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel |
||||
segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. |
||||
*/ |
||||
CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; |
||||
|
||||
/** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object.
|
||||
|
||||
The function return the boundaries of the superpixel segmentation. |
||||
|
||||
@param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, and 0 otherwise. |
||||
@param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border are masked. |
||||
*/ |
||||
CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; |
||||
}; |
||||
|
||||
/** @brief Initializes a ScanSegment object.
|
||||
|
||||
The function initializes a ScanSegment object for the input image. It stores the parameters of |
||||
the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel |
||||
algorithm, which are: num_superpixels, threads, and merge_small. |
||||
|
||||
@param image_width Image width. |
||||
@param image_height Image height. |
||||
@param num_superpixels Desired number of superpixels. Note that the actual number may be smaller |
||||
due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to |
||||
get the actual number. |
||||
@param slices Number of processing threads for parallelisation. Setting -1 uses the maximum number |
||||
of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. |
||||
@param merge_small merge small segments to give the desired number of superpixels. Processing is |
||||
much faster without merging, but many small segments will be left in the image. |
||||
*/ |
||||
CV_EXPORTS_W cv::Ptr<ScanSegment> createScanSegment(int image_width, int image_height, int num_superpixels, int slices = 8, bool merge_small = true); |
||||
|
||||
}} // namespace
|
||||
#endif |
@ -0,0 +1,35 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "perf_precomp.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
|
||||
typedef tuple<Size, MatType> RadonTransformPerfTestParam; |
||||
typedef perf::TestBaseWithParam<RadonTransformPerfTestParam> RadonTransformPerfTest; |
||||
|
||||
PERF_TEST_P(RadonTransformPerfTest, perf, |
||||
testing::Combine( |
||||
testing::Values(TYPICAL_MAT_SIZES), |
||||
testing::Values(CV_8UC1, CV_32FC1, CV_64FC1) |
||||
) |
||||
) |
||||
{ |
||||
Size srcSize = get<0>(GetParam()); |
||||
int srcType = get<1>(GetParam()); |
||||
|
||||
Mat src(srcSize, srcType); |
||||
Mat radon; |
||||
|
||||
declare.in(src, WARMUP_RNG); |
||||
|
||||
TEST_CYCLE() |
||||
{ |
||||
RadonTransform(src, radon); |
||||
} |
||||
|
||||
SANITY_CHECK_NOTHING(); |
||||
} |
||||
|
||||
} } |
@ -0,0 +1,18 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include <opencv2/highgui.hpp> |
||||
#include <opencv2/ximgproc/radon_transform.hpp> |
||||
|
||||
using namespace cv; |
||||
|
||||
int main() { |
||||
Mat src = imread("peilin_plane.png", IMREAD_GRAYSCALE); |
||||
Mat radon; |
||||
ximgproc::RadonTransform(src, radon, 1, 0, 180, false, true); |
||||
imshow("src image", src); |
||||
imshow("Radon transform", radon); |
||||
waitKey(); |
||||
return 0; |
||||
} |
@ -0,0 +1,13 @@ |
||||
# This file is part of OpenCV project. |
||||
# It is subject to the license terms in the LICENSE file found in the top-level directory |
||||
# of this distribution and at http://opencv.org/license.html. |
||||
|
||||
import numpy as np |
||||
import cv2 as cv |
||||
|
||||
if __name__ == "__main__": |
||||
src = cv.imread("peilin_plane.png", cv.IMREAD_GRAYSCALE) |
||||
radon = cv.ximgproc.RadonTransform(src) |
||||
cv.imshow("src image", src) |
||||
cv.imshow("Radon transform", radon) |
||||
cv.waitKey() |
@ -0,0 +1,81 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp" |
||||
|
||||
namespace cv {namespace ximgproc { |
||||
void RadonTransform(InputArray src, |
||||
OutputArray dst, |
||||
double theta, |
||||
double start_angle, |
||||
double end_angle, |
||||
bool crop, |
||||
bool norm) |
||||
{ |
||||
CV_Assert(src.dims() == 2); |
||||
CV_Assert(src.channels() == 1); |
||||
CV_Assert((end_angle - start_angle) * theta > 0); |
||||
|
||||
Mat _srcMat = src.getMat(); |
||||
|
||||
int _row_num, _col_num, _out_mat_type; |
||||
_col_num = cvRound((end_angle - start_angle) / theta); |
||||
transpose(_srcMat, _srcMat); |
||||
Mat _masked_src; |
||||
cv::Point _center; |
||||
|
||||
if (_srcMat.type() == CV_32FC1 || _srcMat.type() == CV_64FC1) { |
||||
_out_mat_type = CV_64FC1; |
||||
} |
||||
else { |
||||
_out_mat_type = CV_32SC1; |
||||
} |
||||
|
||||
if (crop) { |
||||
// crop the source into square
|
||||
_row_num = min(_srcMat.rows, _srcMat.cols); |
||||
cv::Rect _crop_ROI( |
||||
_srcMat.cols / 2 - _row_num / 2, |
||||
_srcMat.rows / 2 - _row_num / 2, |
||||
_row_num, _row_num); |
||||
_srcMat = _srcMat(_crop_ROI); |
||||
// crop the source into circle
|
||||
Mat _mask(_srcMat.size(), CV_8UC1, Scalar(0)); |
||||
_center = Point(_srcMat.cols / 2, _srcMat.rows / 2); |
||||
circle(_mask, _center, _srcMat.cols / 2, Scalar(255), FILLED); |
||||
_srcMat.copyTo(_masked_src, _mask); |
||||
} |
||||
else { |
||||
// avoid cropping corner when rotating
|
||||
_row_num = cvCeil(sqrt(_srcMat.rows * _srcMat.rows + _srcMat.cols * _srcMat.cols)); |
||||
_masked_src = Mat(Size(_row_num, _row_num), _srcMat.type(), Scalar(0)); |
||||
_center = Point(_masked_src.cols / 2, _masked_src.rows / 2); |
||||
_srcMat.copyTo(_masked_src(Rect( |
||||
(_row_num - _srcMat.cols) / 2, |
||||
(_row_num - _srcMat.rows) / 2, |
||||
_srcMat.cols, _srcMat.rows))); |
||||
} |
||||
|
||||
double _t; |
||||
Mat _rotated_src; |
||||
Mat _radon(_row_num, _col_num, _out_mat_type); |
||||
|
||||
for (int _col = 0; _col < _col_num; _col++) { |
||||
// rotate the source by _t
|
||||
_t = (start_angle + _col * theta); |
||||
cv::Mat _r_matrix = cv::getRotationMatrix2D(_center, _t, 1); |
||||
cv::warpAffine(_masked_src, _rotated_src, _r_matrix, _masked_src.size()); |
||||
Mat _col_mat = _radon.col(_col); |
||||
// make projection
|
||||
cv::reduce(_rotated_src, _col_mat, 1, REDUCE_SUM, _out_mat_type); |
||||
} |
||||
|
||||
if (norm) { |
||||
normalize(_radon, _radon, 0, 255, NORM_MINMAX, CV_8UC1); |
||||
} |
||||
|
||||
_radon.copyTo(dst); |
||||
return; |
||||
} |
||||
} } |
@ -0,0 +1,770 @@ |
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com)
|
||||
//
|
||||
//
|
||||
|
||||
#include "precomp.hpp" |
||||
|
||||
#include <numeric> |
||||
#include <atomic> |
||||
|
||||
namespace cv { |
||||
namespace ximgproc { |
||||
|
||||
ScanSegment::~ScanSegment() |
||||
{ |
||||
// nothing
|
||||
} |
||||
|
||||
class ScanSegmentImpl CV_FINAL : public ScanSegment |
||||
{ |
||||
#define UNKNOWN 0 |
||||
#define BORDER -1 |
||||
#define UNCLASSIFIED -2 |
||||
#define NONE -3 |
||||
|
||||
public: |
||||
|
||||
ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small); |
||||
|
||||
virtual ~ScanSegmentImpl(); |
||||
|
||||
virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } |
||||
|
||||
virtual void iterate(InputArray img) CV_OVERRIDE; |
||||
|
||||
virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; |
||||
|
||||
virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; |
||||
|
||||
private: |
||||
static const int neighbourCount = 8; // number of pixel neighbours
|
||||
static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters
|
||||
const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px
|
||||
|
||||
int processthreads; // concurrent threads for parallel processing
|
||||
int width, height; // image size
|
||||
int superpixels; // number of superpixels
|
||||
bool merge; // merge small superpixels
|
||||
int indexSize; // size of label mat vector
|
||||
int clusterSize; // max size of clusters
|
||||
int clusterCount; // number of superpixels from the most recent iterate
|
||||
float adjTolerance; // adjusted colour tolerance
|
||||
|
||||
int horzDiv, vertDiv; // number of horizontal and vertical segments
|
||||
float horzLength, vertLength; // length of each segment
|
||||
int effectivethreads; // effective number of concurrent threads
|
||||
int smallClusters; // clusters below this pixel count are considered small for merging
|
||||
|
||||
cv::AutoBuffer<cv::Rect> seedRects; // autobuffer of seed rectangles
|
||||
cv::AutoBuffer<cv::Rect> seedRectsExt; // autobuffer of extended seed rectangles
|
||||
cv::AutoBuffer<cv::Rect> offsetRects; // autobuffer of offset rectangles
|
||||
cv::Point neighbourLoc[8] = { cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; // neighbour locations
|
||||
|
||||
std::vector<int> indexNeighbourVec; // indices for parallel processing
|
||||
std::vector<std::pair<int, int>> indexProcessVec; |
||||
|
||||
cv::AutoBuffer<int> labelsBuffer; // label autobuffer
|
||||
cv::AutoBuffer<int> clusterBuffer; // cluster autobuffer
|
||||
cv::AutoBuffer<uchar> pixelBuffer; // pixel autobuffer
|
||||
std::vector<cv::AutoBuffer<int>> offsetVec; // vector of offset autobuffers
|
||||
cv::Vec3b* labBuffer; // lab buffer
|
||||
int neighbourLocBuffer[neighbourCount]; // neighbour locations
|
||||
|
||||
std::atomic<int> clusterIndex, clusterID; // atomic indices
|
||||
|
||||
cv::Mat src, labelsMat; // mats
|
||||
|
||||
struct WSNode |
||||
{ |
||||
int next; |
||||
int mask_ofs; |
||||
int img_ofs; |
||||
}; |
||||
|
||||
// Queue for WSNodes
|
||||
struct WSQueue |
||||
{ |
||||
WSQueue() { first = last = 0; } |
||||
int first, last; |
||||
}; |
||||
|
||||
void OP1(int v); |
||||
void OP2(std::pair<int, int> const& p); |
||||
void OP3(int v); |
||||
void OP4(std::pair<int, int> const& p); |
||||
void expandCluster(int* offsetBuffer, const cv::Point& point); |
||||
void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); |
||||
static int allocWSNodes(std::vector<WSNode>& storage); |
||||
static void watershedEx(const cv::Mat& src, cv::Mat& dst); |
||||
}; |
||||
|
||||
CV_EXPORTS Ptr<ScanSegment> createScanSegment(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) |
||||
{ |
||||
return makePtr<ScanSegmentImpl>(image_width, image_height, num_superpixels, slices, merge_small); |
||||
} |
||||
|
||||
ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) |
||||
{ |
||||
// set the number of process threads
|
||||
processthreads = (slices > 0) ? slices : cv::getNumThreads(); |
||||
|
||||
width = image_width; |
||||
height = image_height; |
||||
superpixels = num_superpixels; |
||||
merge = merge_small; |
||||
indexSize = height * width; |
||||
clusterSize = cvRound(1.1f * (float)(width * height) / (float)superpixels); |
||||
clusterCount = 0; |
||||
labelsMat = cv::Mat(height, width, CV_32SC1); |
||||
|
||||
// divide bounds area into uniformly distributed rectangular segments
|
||||
int shortCount = cvFloor(sqrtf((float)processthreads)); |
||||
int longCount = processthreads / shortCount; |
||||
horzDiv = width > height ? longCount : shortCount; |
||||
vertDiv = width > height ? shortCount : longCount; |
||||
horzLength = (float)width / (float)horzDiv; |
||||
vertLength = (float)height / (float)vertDiv; |
||||
effectivethreads = horzDiv * vertDiv; |
||||
smallClusters = 0; |
||||
|
||||
// get array of seed rects
|
||||
seedRects = cv::AutoBuffer<cv::Rect>(horzDiv * vertDiv); |
||||
seedRectsExt = cv::AutoBuffer<cv::Rect>(horzDiv * vertDiv); |
||||
offsetRects = cv::AutoBuffer<cv::Rect>(horzDiv * vertDiv); |
||||
for (int y = 0; y < vertDiv; y++) { |
||||
for (int x = 0; x < horzDiv; x++) { |
||||
int xStart = cvFloor((float)x * horzLength); |
||||
int yStart = cvFloor((float)y * vertLength); |
||||
cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); |
||||
|
||||
int bnd_l = seedRect.x; |
||||
int bnd_t = seedRect.y; |
||||
int bnd_r = seedRect.x + seedRect.width - 1; |
||||
int bnd_b = seedRect.y + seedRect.height - 1; |
||||
if (bnd_l > 0) { |
||||
bnd_l -= 1; |
||||
} |
||||
if (bnd_t > 0) { |
||||
bnd_t -= 1; |
||||
} |
||||
if (bnd_r < width - 1) { |
||||
bnd_r += 1; |
||||
} |
||||
if (bnd_b < height - 1) { |
||||
bnd_b += 1; |
||||
} |
||||
|
||||
seedRects.data()[(y * horzDiv) + x] = seedRect; |
||||
seedRectsExt.data()[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); |
||||
offsetRects.data()[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); |
||||
} |
||||
} |
||||
|
||||
// get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100
|
||||
adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; |
||||
adjTolerance = adjTolerance * adjTolerance; |
||||
|
||||
// create neighbour vector
|
||||
indexNeighbourVec = std::vector<int>(effectivethreads); |
||||
std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); |
||||
|
||||
// create process vector
|
||||
indexProcessVec = std::vector<std::pair<int, int>>(processthreads); |
||||
int processDiv = indexSize / processthreads; |
||||
int processCurrent = 0; |
||||
for (int i = 0; i < processthreads - 1; i++) { |
||||
indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); |
||||
processCurrent += processDiv; |
||||
} |
||||
indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); |
||||
|
||||
// create buffers and initialise
|
||||
labelsBuffer = cv::AutoBuffer<int>(indexSize); |
||||
clusterBuffer = cv::AutoBuffer<int>(indexSize); |
||||
pixelBuffer = cv::AutoBuffer<uchar>(indexSize); |
||||
offsetVec = std::vector<cv::AutoBuffer<int>>(effectivethreads); |
||||
int offsetSize = (clusterSize + 1) * sizeof(int); |
||||
for (int i = 0; i < effectivethreads; i++) { |
||||
offsetVec[i] = cv::AutoBuffer<int>(offsetSize); |
||||
} |
||||
for (int i = 0; i < neighbourCount; i++) { |
||||
neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; |
||||
} |
||||
} |
||||
|
||||
ScanSegmentImpl::~ScanSegmentImpl() |
||||
{ |
||||
// clean up
|
||||
if (!src.empty()) { |
||||
src.release(); |
||||
} |
||||
if (!labelsMat.empty()) { |
||||
labelsMat.release(); |
||||
} |
||||
} |
||||
|
||||
void ScanSegmentImpl::iterate(InputArray img) |
||||
{ |
||||
if (img.isMat()) |
||||
{ |
||||
// get Mat
|
||||
src = img.getMat(); |
||||
|
||||
// image should be valid
|
||||
CV_Assert(!src.empty()); |
||||
} |
||||
else if (img.isMatVector()) |
||||
{ |
||||
std::vector<cv::Mat> vec; |
||||
|
||||
// get vector Mat
|
||||
img.getMatVector(vec); |
||||
|
||||
// array should be valid
|
||||
CV_Assert(!vec.empty()); |
||||
|
||||
// merge into Mat
|
||||
cv::merge(vec, src); |
||||
} |
||||
else |
||||
CV_Error(Error::StsInternal, "Invalid InputArray."); |
||||
|
||||
int depth = src.depth(); |
||||
|
||||
CV_Assert(src.size().width == width && src.size().height == height); |
||||
CV_Assert(depth == CV_8U); |
||||
CV_Assert(src.channels() == 3); |
||||
|
||||
clusterCount = 0; |
||||
clusterIndex.store(0); |
||||
clusterID.store(1); |
||||
|
||||
smallClusters = indexSize / smallClustersDiv; |
||||
|
||||
// set labels to NONE
|
||||
labelsMat.setTo(NONE); |
||||
|
||||
// set labels buffer to UNCLASSIFIED
|
||||
std::fill(labelsBuffer.data(), labelsBuffer.data() + indexSize, UNCLASSIFIED); |
||||
|
||||
// apply light blur
|
||||
cv::medianBlur(src, src, 3); |
||||
|
||||
// start at the center of the rect, then run through the remainder
|
||||
labBuffer = reinterpret_cast<cv::Vec3b*>(src.data); |
||||
cv::parallel_for_(Range(0, (int)indexNeighbourVec.size()), [&](const Range& range) { |
||||
for (int i = range.start; i < range.end; i++) { |
||||
OP1(i); |
||||
} |
||||
}); |
||||
|
||||
if (merge) { |
||||
// get cutoff size for clusters
|
||||
std::vector<std::pair<int, int>> countVec; |
||||
int clusterIndexSize = clusterIndex.load(); |
||||
countVec.reserve(clusterIndexSize / 2); |
||||
for (int i = 1; i < clusterIndexSize; i += 2) { |
||||
int count = clusterBuffer.data()[i]; |
||||
if (count >= smallClusters) { |
||||
int currentID = clusterBuffer.data()[i - 1]; |
||||
countVec.push_back(std::make_pair(currentID, count)); |
||||
} |
||||
} |
||||
|
||||
// sort descending
|
||||
std::sort(countVec.begin(), countVec.end(), [](const std::pair<int, int>& left, const std::pair<int, int>& right) { |
||||
return left.second > right.second; |
||||
}); |
||||
|
||||
int countSize = (int)countVec.size(); |
||||
int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); |
||||
clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair<int, int> p) {return p.second > cutoff; }); |
||||
|
||||
// change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer
|
||||
std::fill_n(clusterBuffer.data(), indexSize, UNKNOWN); |
||||
int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; |
||||
for (int i = 0; i < countLimit; i++) { |
||||
clusterBuffer.data()[countVec[i].first] = i + 1; |
||||
} |
||||
|
||||
parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { |
||||
for (int i = range.start; i < range.end; i++) { |
||||
OP2(indexProcessVec[i]); |
||||
} |
||||
}); |
||||
|
||||
// make copy of labels buffer
|
||||
memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); |
||||
|
||||
// run watershed
|
||||
cv::parallel_for_(Range(0, (int)indexNeighbourVec.size()), [&](const Range& range) { |
||||
for (int i = range.start; i < range.end; i++) { |
||||
OP3(i); |
||||
} |
||||
}); |
||||
|
||||
// copy back to labels mat
|
||||
parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { |
||||
for (int i = range.start; i < range.end; i++) { |
||||
OP4(indexProcessVec[i]); |
||||
} |
||||
}); |
||||
} |
||||
else |
||||
{ |
||||
memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); |
||||
} |
||||
|
||||
src.release(); |
||||
} |
||||
|
||||
void ScanSegmentImpl::OP1(int v) |
||||
{ |
||||
cv::Rect seedRect = seedRects.data()[v]; |
||||
for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { |
||||
for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { |
||||
expandCluster(offsetVec[v].data(), cv::Point(x, y)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ScanSegmentImpl::OP2(std::pair<int, int> const& p) |
||||
{ |
||||
for (int i = p.first; i < p.second; i++) { |
||||
labelsBuffer.data()[i] = clusterBuffer.data()[labelsBuffer.data()[i]]; |
||||
if (labelsBuffer.data()[i] == UNKNOWN) { |
||||
pixelBuffer.data()[i] = 255; |
||||
} |
||||
else { |
||||
pixelBuffer.data()[i] = 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ScanSegmentImpl::OP3(int v) |
||||
{ |
||||
cv::Rect seedRectExt = seedRectsExt.data()[v]; |
||||
cv::Mat seedLabels = labelsMat(seedRectExt).clone(); |
||||
watershedEx(src(seedRectExt), seedLabels); |
||||
seedLabels(offsetRects.data()[v]).copyTo(labelsMat(seedRects.data()[v])); |
||||
seedLabels.release(); |
||||
} |
||||
|
||||
void ScanSegmentImpl::OP4(std::pair<int, int> const& p) |
||||
{ |
||||
for (int i = p.first; i < p.second; i++) { |
||||
if (pixelBuffer.data()[i] == 0) { |
||||
((int*)labelsMat.data)[i] = labelsBuffer.data()[i] - 1; |
||||
} |
||||
else { |
||||
((int*)labelsMat.data)[i] -= 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// expand clusters from a point
|
||||
void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) |
||||
{ |
||||
int pointIndex = (point.y * width) + point.x; |
||||
if (labelsBuffer.data()[pointIndex] == UNCLASSIFIED) { |
||||
int offsetStart = 0; |
||||
int offsetEnd = 0; |
||||
int currentClusterID = clusterID.fetch_add(1); |
||||
|
||||
calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); |
||||
|
||||
if (offsetStart == offsetEnd) { |
||||
labelsBuffer.data()[pointIndex] = UNKNOWN; |
||||
} |
||||
else { |
||||
// set cluster id and get core point index
|
||||
labelsBuffer.data()[pointIndex] = currentClusterID; |
||||
|
||||
while (offsetStart < offsetEnd) { |
||||
int intoffset2 = *(offsetBuffer + offsetStart); |
||||
offsetStart++; |
||||
calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); |
||||
} |
||||
|
||||
// add origin point
|
||||
offsetBuffer[offsetEnd] = pointIndex; |
||||
offsetEnd++; |
||||
|
||||
// store to buffer
|
||||
int currentClusterIndex = clusterIndex.fetch_add(2); |
||||
clusterBuffer.data()[currentClusterIndex] = currentClusterID; |
||||
clusterBuffer.data()[currentClusterIndex + 1] = offsetEnd; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID) |
||||
{ |
||||
for (int i = 0; i < neighbourCount; i++) { |
||||
if (*offsetEnd < clusterSize) { |
||||
int intoffset2 = pointIndex + neighbourLocBuffer[i]; |
||||
if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer.data()[intoffset2] == UNCLASSIFIED) { |
||||
int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; |
||||
int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; |
||||
int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; |
||||
|
||||
if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { |
||||
labelsBuffer.data()[intoffset2] = currentClusterID; |
||||
offsetBuffer[*offsetEnd] = intoffset2; |
||||
(*offsetEnd)++; |
||||
} |
||||
} |
||||
} |
||||
else { break; } |
||||
} |
||||
} |
||||
|
||||
int ScanSegmentImpl::allocWSNodes(std::vector<ScanSegmentImpl::WSNode>& storage) |
||||
{ |
||||
int sz = (int)storage.size(); |
||||
int newsz = MAX(128, sz * 3 / 2); |
||||
|
||||
storage.resize(newsz); |
||||
if (sz == 0) |
||||
{ |
||||
storage[0].next = 0; |
||||
sz = 1; |
||||
} |
||||
for (int i = sz; i < newsz - 1; i++) |
||||
storage[i].next = i + 1; |
||||
storage[newsz - 1].next = 0; |
||||
return sz; |
||||
} |
||||
|
||||
//the modified version of watershed algorithm from OpenCV
|
||||
void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) |
||||
{ |
||||
// https://github.com/Seaball/watershed_with_mask
|
||||
|
||||
// Labels for pixels
|
||||
const int IN_QUEUE = -2; // Pixel visited
|
||||
// possible bit values = 2^8
|
||||
const int NQ = 256; |
||||
|
||||
cv::Size size = src.size(); |
||||
int channel = 3; |
||||
// Vector of every created node
|
||||
std::vector<WSNode> storage; |
||||
int free_node = 0, node; |
||||
// Priority queue of queues of nodes
|
||||
// from high priority (0) to low priority (255)
|
||||
WSQueue q[NQ]; |
||||
// Non-empty queue with highest priority
|
||||
int active_queue; |
||||
int i, j; |
||||
// Color differences
|
||||
int db, dg, dr; |
||||
int subs_tab[513]; |
||||
|
||||
// MAX(a,b) = b + MAX(a-b,0)
|
||||
#define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ]) |
||||
// MIN(a,b) = a - MAX(a-b,0)
|
||||
#define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) |
||||
|
||||
// Create a new node with offsets mofs and iofs in queue idx
|
||||
#define ws_push(idx,mofs,iofs) \ |
||||
{ \
|
||||
if (!free_node) \
|
||||
free_node = allocWSNodes(storage); \
|
||||
node = free_node; \
|
||||
free_node = storage[free_node].next; \
|
||||
storage[node].next = 0; \
|
||||
storage[node].mask_ofs = mofs; \
|
||||
storage[node].img_ofs = iofs; \
|
||||
if (q[idx].last) \
|
||||
storage[q[idx].last].next = node; \
|
||||
else \
|
||||
q[idx].first = node; \
|
||||
q[idx].last = node; \
|
||||
} |
||||
|
||||
// Get next node from queue idx
|
||||
#define ws_pop(idx,mofs,iofs) \ |
||||
{ \
|
||||
node = q[idx].first; \
|
||||
q[idx].first = storage[node].next; \
|
||||
if (!storage[node].next) \
|
||||
q[idx].last = 0; \
|
||||
storage[node].next = free_node; \
|
||||
free_node = node; \
|
||||
mofs = storage[node].mask_ofs; \
|
||||
iofs = storage[node].img_ofs; \
|
||||
} |
||||
|
||||
// Get highest absolute channel difference in diff
|
||||
#define c_diff(ptr1,ptr2,diff) \ |
||||
{ \
|
||||
db = std::abs((ptr1)[0] - (ptr2)[0]); \
|
||||
dg = std::abs((ptr1)[1] - (ptr2)[1]); \
|
||||
dr = std::abs((ptr1)[2] - (ptr2)[2]); \
|
||||
diff = ws_max(db, dg); \
|
||||
diff = ws_max(diff, dr); \
|
||||
CV_Assert(0 <= diff && diff <= 255); \
|
||||
} |
||||
|
||||
CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); |
||||
CV_Assert(src.size() == dst.size()); |
||||
|
||||
// Current pixel in input image
|
||||
const uchar* img = src.ptr(); |
||||
// Step size to next row in input image
|
||||
int istep = int(src.step / sizeof(img[0])); |
||||
|
||||
// Current pixel in mask image
|
||||
int* mask = dst.ptr<int>(); |
||||
// Step size to next row in mask image
|
||||
int mstep = int(dst.step / sizeof(mask[0])); |
||||
|
||||
for (i = 0; i < 256; i++) |
||||
subs_tab[i] = 0; |
||||
for (i = 256; i <= 512; i++) |
||||
subs_tab[i] = i - 256; |
||||
|
||||
//for (j = 0; j < size.width; j++)
|
||||
//mask[j] = mask[j + mstep*(size.height - 1)] = 0;
|
||||
|
||||
// initial phase: put all the neighbor pixels of each marker to the ordered queue -
|
||||
// determine the initial boundaries of the basins
|
||||
for (i = 1; i < size.height - 1; i++) { |
||||
img += istep; mask += mstep; |
||||
mask[0] = mask[size.width - 1] = 0; // boundary pixels
|
||||
|
||||
for (j = 1; j < size.width - 1; j++) { |
||||
int* m = mask + j; |
||||
if (m[0] < 0) |
||||
m[0] = 0; |
||||
if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) |
||||
{ |
||||
// Find smallest difference to adjacent markers
|
||||
const uchar* ptr = img + j * channel; |
||||
int idx = 256, t; |
||||
if (m[-1] > 0) { |
||||
c_diff(ptr, ptr - channel, idx); |
||||
} |
||||
if (m[1] > 0) { |
||||
c_diff(ptr, ptr + channel, t); |
||||
idx = ws_min(idx, t); |
||||
} |
||||
if (m[-mstep] > 0) { |
||||
c_diff(ptr, ptr - istep, t); |
||||
idx = ws_min(idx, t); |
||||
} |
||||
if (m[mstep] > 0) { |
||||
c_diff(ptr, ptr + istep, t); |
||||
idx = ws_min(idx, t); |
||||
} |
||||
|
||||
// Add to according queue
|
||||
CV_Assert(0 <= idx && idx <= 255); |
||||
ws_push(idx, i * mstep + j, i * istep + j * channel); |
||||
m[0] = IN_QUEUE;//initial unvisited
|
||||
} |
||||
} |
||||
} |
||||
// find the first non-empty queue
|
||||
for (i = 0; i < NQ; i++) |
||||
if (q[i].first) |
||||
break; |
||||
|
||||
// if there is no markers, exit immediately
|
||||
if (i == NQ) |
||||
return; |
||||
|
||||
active_queue = i;//first non-empty priority queue
|
||||
img = src.ptr(); |
||||
mask = dst.ptr<int>(); |
||||
|
||||
// recursively fill the basins
|
||||
int diff = 0, temp = 0; |
||||
for (;;) |
||||
{ |
||||
int mofs, iofs; |
||||
int lab = 0, t; |
||||
int* m; |
||||
const uchar* ptr; |
||||
|
||||
// Get non-empty queue with highest priority
|
||||
// Exit condition: empty priority queue
|
||||
if (q[active_queue].first == 0) |
||||
{ |
||||
for (i = active_queue + 1; i < NQ; i++) |
||||
if (q[i].first) |
||||
break; |
||||
if (i == NQ) |
||||
{ |
||||
std::vector<WSNode>().swap(storage); |
||||
break; |
||||
} |
||||
active_queue = i; |
||||
} |
||||
|
||||
// Get next node
|
||||
ws_pop(active_queue, mofs, iofs); |
||||
int top = 1, bottom = 1, left = 1, right = 1; |
||||
if (0 <= mofs && mofs < mstep)//pixel on the top
|
||||
top = 0; |
||||
if ((mofs % mstep) == 0)//pixel in the left column
|
||||
left = 0; |
||||
if ((mofs + 1) % mstep == 0)//pixel in the right column
|
||||
right = 0; |
||||
if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom
|
||||
bottom = 0; |
||||
|
||||
// Calculate pointer to current pixel in input and marker image
|
||||
m = mask + mofs; |
||||
ptr = img + iofs; |
||||
// Check surrounding pixels for labels to determine label for current pixel
|
||||
if (left) {//the left point can be visited
|
||||
t = m[-1]; |
||||
if (t > 0) { |
||||
lab = t; |
||||
c_diff(ptr, ptr - channel, diff); |
||||
} |
||||
} |
||||
if (right) {// Right point can be visited
|
||||
t = m[1]; |
||||
if (t > 0) { |
||||
if (lab == 0) {//and this point didn't be labeled before
|
||||
lab = t; |
||||
c_diff(ptr, ptr + channel, diff); |
||||
} |
||||
else if (t != lab) { |
||||
c_diff(ptr, ptr + channel, temp); |
||||
diff = ws_min(diff, temp); |
||||
if (diff == temp) |
||||
lab = t; |
||||
} |
||||
} |
||||
} |
||||
if (top) { |
||||
t = m[-mstep]; // Top
|
||||
if (t > 0) { |
||||
if (lab == 0) {//and this point didn't be labeled before
|
||||
lab = t; |
||||
c_diff(ptr, ptr - istep, diff); |
||||
} |
||||
else if (t != lab) { |
||||
c_diff(ptr, ptr - istep, temp); |
||||
diff = ws_min(diff, temp); |
||||
if (diff == temp) |
||||
lab = t; |
||||
} |
||||
} |
||||
} |
||||
if (bottom) { |
||||
t = m[mstep]; // Bottom
|
||||
if (t > 0) { |
||||
if (lab == 0) { |
||||
lab = t; |
||||
} |
||||
else if (t != lab) { |
||||
c_diff(ptr, ptr + istep, temp); |
||||
diff = ws_min(diff, temp); |
||||
if (diff == temp) |
||||
lab = t; |
||||
} |
||||
} |
||||
} |
||||
// Set label to current pixel in marker image
|
||||
CV_Assert(lab != 0);//lab must be labeled with a nonzero number
|
||||
m[0] = lab; |
||||
|
||||
// Add adjacent, unlabeled pixels to corresponding queue
|
||||
if (left) { |
||||
if (m[-1] == 0)//left pixel with marker 0
|
||||
{ |
||||
c_diff(ptr, ptr - channel, t); |
||||
ws_push(t, mofs - 1, iofs - channel); |
||||
active_queue = ws_min(active_queue, t); |
||||
m[-1] = IN_QUEUE; |
||||
} |
||||
} |
||||
|
||||
if (right) |
||||
{ |
||||
if (m[1] == 0)//right pixel with marker 0
|
||||
{ |
||||
c_diff(ptr, ptr + channel, t); |
||||
ws_push(t, mofs + 1, iofs + channel); |
||||
active_queue = ws_min(active_queue, t); |
||||
m[1] = IN_QUEUE; |
||||
} |
||||
} |
||||
|
||||
if (top) |
||||
{ |
||||
if (m[-mstep] == 0)//top pixel with marker 0
|
||||
{ |
||||
c_diff(ptr, ptr - istep, t); |
||||
ws_push(t, mofs - mstep, iofs - istep); |
||||
active_queue = ws_min(active_queue, t); |
||||
m[-mstep] = IN_QUEUE; |
||||
} |
||||
} |
||||
|
||||
if (bottom) { |
||||
if (m[mstep] == 0)//down pixel with marker 0
|
||||
{ |
||||
c_diff(ptr, ptr + istep, t); |
||||
ws_push(t, mofs + mstep, iofs + istep); |
||||
active_queue = ws_min(active_queue, t); |
||||
m[mstep] = IN_QUEUE; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ScanSegmentImpl::getLabels(OutputArray labels_out) |
||||
{ |
||||
labels_out.assign(labelsMat); |
||||
} |
||||
|
||||
void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) |
||||
{ |
||||
image.create(height, width, CV_8UC1); |
||||
cv::Mat dst = image.getMat(); |
||||
dst.setTo(cv::Scalar(0)); |
||||
|
||||
const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; |
||||
const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; |
||||
|
||||
for (int j = 0; j < height; j++) |
||||
{ |
||||
for (int k = 0; k < width; k++) |
||||
{ |
||||
int neighbors = 0; |
||||
for (int i = 0; i < 8; i++) |
||||
{ |
||||
int x = k + dx8[i]; |
||||
int y = j + dy8[i]; |
||||
|
||||
if ((x >= 0 && x < width) && (y >= 0 && y < height)) |
||||
{ |
||||
int index = y * width + x; |
||||
int mainindex = j * width + k; |
||||
if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) |
||||
{ |
||||
if (thick_line || !*dst.ptr<uchar>(y, x)) |
||||
neighbors++; |
||||
} |
||||
} |
||||
} |
||||
if (neighbors > 1) |
||||
*dst.ptr<uchar>(j, k) = (uchar)255; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} // namespace ximgproc
|
||||
} // namespace cv
|
@ -0,0 +1,81 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp" |
||||
|
||||
namespace opencv_test {namespace { |
||||
|
||||
TEST(RadonTransformTest, output_size) |
||||
{ |
||||
Mat src(Size(256, 256), CV_8U, Scalar(0)); |
||||
circle(src, Point(128, 128), 64, Scalar(255), FILLED); |
||||
Mat radon; |
||||
cv::ximgproc::RadonTransform(src, radon); |
||||
|
||||
EXPECT_EQ(363, radon.rows); |
||||
EXPECT_EQ(180, radon.cols); |
||||
|
||||
cv::ximgproc::RadonTransform(src, radon, 1, 0, 180, true); |
||||
|
||||
EXPECT_EQ(256, radon.rows); |
||||
EXPECT_EQ(180, radon.cols); |
||||
} |
||||
|
||||
TEST(RadonTransformTest, output_type) |
||||
{ |
||||
Mat src_int(Size(256, 256), CV_8U, Scalar(0)); |
||||
circle(src_int, Point(128, 128), 64, Scalar(255), FILLED); |
||||
Mat radon, radon_norm; |
||||
cv::ximgproc::RadonTransform(src_int, radon); |
||||
cv::ximgproc::RadonTransform(src_int, radon_norm, 1, 0, 180, false, true); |
||||
|
||||
EXPECT_EQ(CV_32SC1, radon.type()); |
||||
EXPECT_EQ(CV_8U, radon_norm.type()); |
||||
|
||||
Mat src_float(Size(256, 256), CV_32FC1, Scalar(0)); |
||||
Mat src_double(Size(256, 256), CV_32FC1, Scalar(0)); |
||||
cv::ximgproc::RadonTransform(src_float, radon); |
||||
cv::ximgproc::RadonTransform(src_float, radon_norm, 1, 0, 180, false, true); |
||||
EXPECT_EQ(CV_64FC1, radon.type()); |
||||
EXPECT_EQ(CV_8U, radon_norm.type()); |
||||
cv::ximgproc::RadonTransform(src_double, radon); |
||||
EXPECT_EQ(CV_64FC1, radon.type()); |
||||
EXPECT_EQ(CV_8U, radon_norm.type()); |
||||
} |
||||
|
||||
TEST(RadonTransformTest, accuracy_by_pixel) |
||||
{ |
||||
Mat src(Size(256, 256), CV_8U, Scalar(0)); |
||||
circle(src, Point(128, 128), 64, Scalar(255), FILLED); |
||||
Mat radon; |
||||
cv::ximgproc::RadonTransform(src, radon); |
||||
|
||||
ASSERT_EQ(CV_32SC1, radon.type()); |
||||
|
||||
EXPECT_EQ(0, radon.at<int>(0, 0)); |
||||
|
||||
EXPECT_LT(18000, radon.at<int>(128, 128)); |
||||
EXPECT_GT(19000, radon.at<int>(128, 128)); |
||||
} |
||||
|
||||
TEST(RadonTransformTest, accuracy_uchar) |
||||
{ |
||||
Mat src(Size(10, 10), CV_8UC1, Scalar(1)); |
||||
cv::Mat radon; |
||||
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false); |
||||
|
||||
EXPECT_EQ(100, sum(radon.col(0))[0]); |
||||
} |
||||
|
||||
TEST(RadonTransformTest, accuracy_float) |
||||
{ |
||||
Mat src(Size(10, 10), CV_32FC1, Scalar(1.1)); |
||||
cv::Mat radon; |
||||
ximgproc::RadonTransform(src, radon, 45, 0, 180, false, false); |
||||
|
||||
EXPECT_LT(109, sum(radon.col(0))[0]); |
||||
EXPECT_GT(111, sum(radon.col(0))[0]); |
||||
} |
||||
|
||||
} } |
@ -0,0 +1,35 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
|
||||
static void runScanSegment(int slices) |
||||
|
||||
{ |
||||
Mat img = imread(cvtest::findDataFile("cv/shared/lena.png"), IMREAD_COLOR); |
||||
Mat labImg; |
||||
cvtColor(img, labImg, COLOR_BGR2Lab); |
||||
Ptr<ScanSegment> ss = createScanSegment(labImg.cols, labImg.rows, 500, slices, true); |
||||
ss->iterate(labImg); |
||||
int numSuperpixels = ss->getNumberOfSuperpixels(); |
||||
EXPECT_GT(numSuperpixels, 100); |
||||
EXPECT_LE(numSuperpixels, 500); |
||||
Mat res; |
||||
ss->getLabelContourMask(res, false); |
||||
EXPECT_GE(cvtest::norm(res, NORM_L1), 1000000); |
||||
|
||||
if (cvtest::debugLevel >= 10) |
||||
{ |
||||
imshow("ScanSegment", res); |
||||
waitKey(); |
||||
} |
||||
} |
||||
|
||||
TEST(ximgproc_ScanSegment, smoke) { runScanSegment(1); } |
||||
TEST(ximgproc_ScanSegment, smoke4) { runScanSegment(4); } |
||||
TEST(ximgproc_ScanSegment, smoke8) { runScanSegment(8); } |
||||
|
||||
}} // namespace
|