当前位置:网站首页>Code and principle of RANSAC
Code and principle of RANSAC
2022-06-27 19:17:00 【Wang bupian】
One . RANSAC Code principle
:https://blog.csdn.net/robinhjwy/article/details/79174914
Two . RANSAC Of Opencv Code
Only excerpts and RANSAC Relevant part , Extract from :https://blog.csdn.net/Darlingqiang/article/details/79775542
- OpenCV This function is implemented by calling findHomography Function call , Here is a routine :
[cpp] view plain copy
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat obj=imread("F:\\Picture\\obj.jpg"); // Load target image
Mat scene=imread("F:\\Picture\\scene.jpg"); // Load scene image
if (obj.empty() || scene.empty() )
{
cout<<"Can't open the picture!\n";
return 0;
}
vector<KeyPoint> obj_keypoints,scene_keypoints;
Mat obj_descriptors,scene_descriptors;
ORB detector; // use ORB The algorithm extracts feature points
detector.detect(obj,obj_keypoints);
detector.detect(scene,scene_keypoints);
detector.compute(obj,obj_keypoints,obj_descriptors);
detector.compute(scene,scene_keypoints,scene_descriptors);
BFMatcher matcher(NORM_HAMMING,true); // Hamming distance as a measure of similarity
vector<DMatch> matches;
matcher.match(obj_descriptors, scene_descriptors, matches);
Mat match_img;
drawMatches(obj,obj_keypoints,scene,scene_keypoints,matches,match_img);
imshow(" Before filtering out mismatches ",match_img);
// Save matching sequence number
vector<int> queryIdxs( matches.size() ), trainIdxs( matches.size() );
for( size_t i = 0; i < matches.size(); i++ )
{
queryIdxs[i] = matches[i].queryIdx;
trainIdxs[i] = matches[i].trainIdx;
}
Mat H12; // Transformation matrix
vector<Point2f> points1; KeyPoint::convert(obj_keypoints, points1, queryIdxs);
vector<Point2f> points2; KeyPoint::convert(scene_keypoints, points2, trainIdxs);
int ransacReprojThreshold = 5; // Reject threshold
H12 = findHomography( Mat(points1), Mat(points2), CV_RANSAC, ransacReprojThreshold );
vector<char> matchesMask( matches.size(), 0 );
Mat points1t;
perspectiveTransform(Mat(points1), points1t, H12);
for( size_t i1 = 0; i1 < points1.size(); i1++ ) // preservation ‘ Inside point ’
{
if( norm(points2[i1] - points1t.at<Point2f>((int)i1,0)) <= ransacReprojThreshold ) // Mark the inner point
{
matchesMask[i1] = 1;
}
}
Mat match_img2; // Filter out ‘ External point ’ after
drawMatches(obj,obj_keypoints,scene,scene_keypoints,matches,match_img2,Scalar(0,0,255),Scalar::all(-1),matchesMask);
// Draw the target position
std::vector<Point2f> obj_corners(4);
obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( obj.cols, 0 );
obj_corners[2] = cvPoint( obj.cols, obj.rows ); obj_corners[3] = cvPoint( 0, obj.rows );
std::vector<Point2f> scene_corners(4);
perspectiveTransform( obj_corners, scene_corners, H12);
line( match_img2, scene_corners[0] + Point2f(static_cast<float>(obj.cols), 0),
scene_corners[1] + Point2f(static_cast<float>(obj.cols), 0),Scalar(0,0,255),2);
line( match_img2, scene_corners[1] + Point2f(static_cast<float>(obj.cols), 0),
scene_corners[2] + Point2f(static_cast<float>(obj.cols), 0),Scalar(0,0,255),2);
line( match_img2, scene_corners[2] + Point2f(static_cast<float>(obj.cols), 0),
scene_corners[3] + Point2f(static_cast<float>(obj.cols), 0),Scalar(0,0,255),2);
line( match_img2, scene_corners[3] + Point2f(static_cast<float>(obj.cols), 0),
scene_corners[0] + Point2f(static_cast<float>(obj.cols), 0),Scalar(0,0,255),2);
imshow(" After filtering out mismatches ",match_img2);
waitKey(0);
return 0;
}
- RANSAC The source code parsing
(1)findHomography Internal calls cvFindHomography function
srcPoints: Target image point set
dstPoints: Scene image point set
method: Minimum median method 、RANSAC Method 、 Least square method
ransacReprojTheshold: Projection error threshold
mask: Mask
[cpp] view plain copy
cvFindHomography( const CvMat* objectPoints, const CvMat* imagePoints,
CvMat* __H, int method, double ransacReprojThreshold,
CvMat* mask )
{
const double confidence = 0.995; // Degree of confidence
const int maxIters = 2000; // The maximum number of iterations
const double defaultRANSACReprojThreshold = 3; // Default reject threshold
bool result = false;
Ptr<CvMat> m, M, tempMask;
double H[9];
CvMat matH = cvMat( 3, 3, CV_64FC1, H ); // Transformation matrix
int count;
CV_Assert( CV_IS_MAT(imagePoints) && CV_IS_MAT(objectPoints) );
count = MAX(imagePoints->cols, imagePoints->rows);
CV_Assert( count >= 4 ); // There are at least 4 Samples
if( ransacReprojThreshold <= 0 )
ransacReprojThreshold = defaultRANSACReprojThreshold;
m = cvCreateMat( 1, count, CV_64FC2 ); // Convert to homogeneous coordinates
cvConvertPointsHomogeneous( imagePoints, m );
M = cvCreateMat( 1, count, CV_64FC2 );// Convert to homogeneous coordinates
cvConvertPointsHomogeneous( objectPoints, M );
if( mask )
{
CV_Assert( CV_IS_MASK_ARR(mask) && CV_IS_MAT_CONT(mask->type) &&
(mask->rows == 1 || mask->cols == 1) &&
mask->rows*mask->cols == count );
}
if( mask || count > 4 )
tempMask = cvCreateMat( 1, count, CV_8U );
if( !tempMask.empty() )
cvSet( tempMask, cvScalarAll(1.) );
CvHomographyEstimator estimator(4);
if( count == 4 )
method = 0;
if( method == CV_LMEDS ) // Minimum median method
result = estimator.runLMeDS( M, m, &matH, tempMask, confidence, maxIters );
else if( method == CV_RANSAC ) // use RANSAC Algorithm
result = estimator.runRANSAC( M, m, &matH, tempMask, ransacReprojThreshold, confidence, maxIters);//(2) analysis
else
result = estimator.runKernel( M, m, &matH ) > 0; // Least square method
if( result && count > 4 )
{
icvCompressPoints( (CvPoint2D64f*)M->data.ptr, tempMask->data.ptr, 1, count ); // Save the inner point set
count = icvCompressPoints( (CvPoint2D64f*)m->data.ptr, tempMask->data.ptr, 1, count );
M->cols = m->cols = count;
if( method == CV_RANSAC ) //
estimator.runKernel( M, m, &matH ); // The transformation matrix is re estimated by the least square method on the inner point set
estimator.refine( M, m, &matH, 10 );//
}
if( result )
cvConvert( &matH, __H ); // Save the transformation matrix
if( mask && tempMask )
{
if( CV_ARE_SIZES_EQ(mask, tempMask) )
cvCopy( tempMask, mask );
else
cvTranspose( tempMask, mask );
}
return (int)result;
}
(2) runRANSAC
maxIters: Maximum number of iterations
m1,m2 : Data samples
model: Transformation matrix
reprojThreshold: Projection error threshold
confidence: Degree of confidence 0.995
[cpp] view plain copy
bool CvModelEstimator2::runRANSAC( const CvMat* m1, const CvMat* m2, CvMat* model,
CvMat* mask0, double reprojThreshold,
double confidence, int maxIters )
{
bool result = false;
cv::Ptr<CvMat> mask = cvCloneMat(mask0);
cv::Ptr<CvMat> models, err, tmask;
cv::Ptr<CvMat> ms1, ms2;
int iter, niters = maxIters;
int count = m1->rows*m1->cols, maxGoodCount = 0;
CV_Assert( CV_ARE_SIZES_EQ(m1, m2) && CV_ARE_SIZES_EQ(m1, mask) );
if( count < modelPoints )
return false;
models = cvCreateMat( modelSize.height*maxBasicSolutions, modelSize.width, CV_64FC1 );
err = cvCreateMat( 1, count, CV_32FC1 );// Save the projection error corresponding to each group of points
tmask = cvCreateMat( 1, count, CV_8UC1 );
if( count > modelPoints ) // More than 4 A little bit
{
ms1 = cvCreateMat( 1, modelPoints, m1->type );
ms2 = cvCreateMat( 1, modelPoints, m2->type );
}
else // be equal to 4 A little bit
{
niters = 1; // One iteration
ms1 = cvCloneMat(m1); // Save every randomly found sample point
ms2 = cvCloneMat(m2);
}
for( iter = 0; iter < niters; iter++ ) // Continuous iteration
{
int i, goodCount, nmodels;
if( count > modelPoints )
{
// stay (3) analysis getSubset
bool found = getSubset( m1, m2, ms1, ms2, 300 ); // Random selection 4 Group point , And the three points are not collinear ,(3) analysis
if( !found )
{
if( iter == 0 )
return false;
break;
}
}
nmodels = runKernel( ms1, ms2, models ); // Estimate the approximate transformation matrix , return 1
if( nmodels <= 0 )
continue;
for( i = 0; i < nmodels; i++ )// Do it once
{
CvMat model_i;
cvGetRows( models, &model_i, i*modelSize.height, (i+1)*modelSize.height );// obtain 3×3 Matrix elements
goodCount = findInliers( m1, m2, &model_i, err, tmask, reprojThreshold ); // Find the inner point ,(4) analysis
if( goodCount > MAX(maxGoodCount, modelPoints-1) ) // The number of elements in the current interior point set is greater than the number of elements in the optimal interior point set
{
std::swap(tmask, mask);
cvCopy( &model_i, model ); // Update the optimal model
maxGoodCount = goodCount;
//confidence Default for confidence 0.995,modelPoints Is the minimum number of samples required =4,niters The number of iterations
niters = cvRANSACUpdateNumIters( confidence, // Update iterations ,(5) analysis
(double)(count - goodCount)/count, modelPoints, niters );
}
}
}
if( maxGoodCount > 0 )
{
if( mask != mask0 )
cvCopy( mask, mask0 );
result = true;
}
return result;
}
step one niters The initial value is 2000, This is the initial RANSAC The number of cycles of the algorithm ,getSubset() Functions are randomly selected from a set of corresponding sequences 4 Group ( Because if you want to calculate a 3X3 Matrix , Need at least 4 Coordinates corresponding to the group ),m1 and m2 We input the sequence ,ms1 and ms2 Is a random selection of the corresponding 4 Group matching .
Choose at random 4 After group matching , It should be based on this 4 A match calculates the corresponding matrix , So the function runKernel() It is based on 4 Group matching calculation matrix , In the parameter models Is the matrix .
stept wo This matrix is just an assumption , To test this hypothesis , Need to use other points to calculate , See if the other points are inner points or outer points .
findInliers() Function is used to calculate the interior point . Using the matrix obtained above , Bring all the sequences into , Calculate which are interior points , What are the outliers , The return value of the function is goodCount, Is the number of interior points calculated this time .
step three Another value in the function is maxGoodCount, The maximum number of interior points per cycle is stored in this value , If an estimated matrix has more interior points , The more likely this matrix is to be correct .
step four So after calculating the number of interior points , Then judge goodCount and maxGoodCount The size of the relationship , If goodCount>maxGoodCount, Then put goodCount Assign a value to maxGoodCount. The one line of code after the assignment is critical , Let's talk about it alone , The code is as follows :
niters = cvRANSACUpdateNumIters( confidence,
(double)(count - goodCount)/count, modelPoints, niters );
niters It was originally the number of iterations , That is, the number of cycles . But through this line of code, we find that , After each cycle , Will be right. niters This value is updated , That is, the total number of cycles will be changed after each cycle .
cvRANSACUpdateNumIters() Functions use confidence( Degree of confidence )count( Total number of matches )goodCount( Current number of interior points )niters( Current total iterations ) These parameters , To dynamically change the total number of iterations . The central idea of this function is that when the interior points account for a large proportion , Then it's likely that the correct estimate has been found , So reduce the number of iterations to save time . The number of iterations is reduced exponentially , So the time cost saved is also very considerable . So the original design 2000 Iterations of , Maybe the final number of iterations is only a few tens . alike , If you set the number of iterations to 10000 Or bigger , After several iterations ,niters It will become very small again . So at the beginning niters No matter how big it is , In fact, it has no effect on the final running time .
therefore , You should now know why the input data has increased , The running time of the algorithm will not increase .openCV Of RANSAC The algorithm first sets the number of iterations to 2000, And then in the process of iteration , Change the total number of iterations dynamically , No matter how much data is entered , The total number of iterations does not increase , And through 4 The time of the matrix estimated by matching calculation is constant , The interior point is calculated by estimating the matrix , The increased time cost in this area can be basically ignored . So the end result is , No matter how many input points , The operation time will not change much .
That's all RANSAC The core code of the algorithm , Some of the functions used , The following are given one by one .
(3)getSubset
ms1,ms2: Save randomly found 4 Samples
maxAttempts: Maximum number of iterations ,300
[cpp] view plain copy
bool CvModelEstimator2::getSubset( const CvMat* m1, const CvMat* m2,
CvMat* ms1, CvMat* ms2, int maxAttempts )
{
cv::AutoBuffer<int> _idx(modelPoints); //modelPoints The minimum number of sample points required
int* idx = _idx;
int i = 0, j, k, idx_i, iters = 0;
int type = CV_MAT_TYPE(m1->type), elemSize = CV_ELEM_SIZE(type);
const int *m1ptr = m1->data.i, *m2ptr = m2->data.i;
int *ms1ptr = ms1->data.i, *ms2ptr = ms2->data.i;
int count = m1->cols*m1->rows;
assert( CV_IS_MAT_CONT(m1->type & m2->type) && (elemSize % sizeof(int) == 0) );
elemSize /= sizeof(int); // The number of bytes occupied by each data
for(; iters < maxAttempts; iters++)
{
for( i = 0; i < modelPoints && iters < maxAttempts; )
{
idx[i] = idx_i = cvRandInt(&rng) % count; // Random selection 1 Group point
for( j = 0; j < i; j++ ) // Check whether the selection is repeated
if( idx_i == idx[j] )
break;
if( j < i )
continue; // To choose
for( k = 0; k < elemSize; k++ ) // Copy point data
{
ms1ptr[i*elemSize + k] = m1ptr[idx_i*elemSize + k];
ms2ptr[i*elemSize + k] = m2ptr[idx_i*elemSize + k];
}
if( checkPartialSubsets && (!checkSubset( ms1, i+1 ) || !checkSubset( ms2, i+1 )))// Check whether the points are collinear
{
iters++; // If collinear , Re select a group
continue;
}
i++;
}
if( !checkPartialSubsets && i == modelPoints &&
(!checkSubset( ms1, i ) || !checkSubset( ms2, i )))
continue;
break;
}
return i == modelPoints && iters < maxAttempts; // Returns the number of sample points found
}
(4) findInliers & computerReprojError
[cpp] view plain copy
int CvModelEstimator2::findInliers( const CvMat* m1, const CvMat* m2,
const CvMat* model, CvMat* _err,
CvMat* _mask, double threshold )
{
int i, count = _err->rows*_err->cols, goodCount = 0;
const float* err = _err->data.fl;
uchar* mask = _mask->data.ptr;
computeReprojError( m1, m2, model, _err ); // Calculate the projection error of each group of points
threshold *= threshold;
for( i = 0; i < count; i++ )
goodCount += mask[i] = err[i] <= threshold;// The error is within the limit , Join in ‘ Interior point set ’
return goodCount;
}
//--------------------------------------
void CvHomographyEstimator::computeReprojError( const CvMat* m1, const CvMat* m2,const CvMat* model, CvMat* _err )
{
int i, count = m1->rows*m1->cols;
const CvPoint2D64f* M = (const CvPoint2D64f*)m1->data.ptr;
const CvPoint2D64f* m = (const CvPoint2D64f*)m2->data.ptr;
const double* H = model->data.db;
float* err = _err->data.fl;
for( i = 0; i < count; i++ ) // Save the projection error of each group of points , Corresponding to the above transformation formula
{
double ww = 1./(H[6]*M[i].x + H[7]*M[i].y + 1.);
double dx = (H[0]*M[i].x + H[1]*M[i].y + H[2])*ww - m[i].x;
double dy = (H[3]*M[i].x + H[4]*M[i].y + H[5])*ww - m[i].y;
err[i] = (float)(dx*dx + dy*dy);
}
}
(5)cvRANSACUpdateNumIters
Corresponding to the above k Calculation formula
p: Degree of confidence
ep: Outer point ratio
[cpp] view plain copy
cvRANSACUpdateNumIters( double p, double ep,
int model_points, int max_iters )
{
if( model_points <= 0 )
CV_Error( CV_StsOutOfRange, "the number of model points should be positive" );
p = MAX(p, 0.);
p = MIN(p, 1.);
ep = MAX(ep, 0.);
ep = MIN(ep, 1.);
// avoid inf's & nan's
double num = MAX(1. - p, DBL_MIN); //num=1-p, Make molecules
double denom = 1. - pow(1. - ep,model_points);// Be a denominator
if( denom < DBL_MIN )
return 0;
num = log(num);
denom = log(denom);
return denom >= 0 || -num >= max_iters*(-denom) ?
max_iters : cvRound(num/denom);
}
Reference resources https://blog.csdn.net/laobai1015/article/details/51683076
cv::findFundamentalMat
The knowledge of epipolar geometry is often used in processing stereo image pairs , It is also very common to calculate the basic matrix .OpenCV The algorithm of basic matrix is realized . For the old version of C Code , Calculate the fundamental matrix RANSAC There is a method in which the number of iterations is not convergent bug, It may cause the number of samples per calculation to reach the maximum limit , The root cause is that the formula for calculating the sampling reliability is wrong , The new version of the code has not been looked at carefully , I'm not sure if I've fixed this bug. But this bug It doesn't make much difference to the final result , It will only affect the efficiency of calculation —— Originally, a few samples can end the iteration , In this bug It may take hundreds of samples under the influence of .
There are also some problems in the use of the new version of the function to calculate the basic matrix , So let's see cv::findFundamentalMat function :
//! the algorithm for finding fundamental matrix
enum
{
FM_7POINT = CV_FM_7POINT, //!< 7-point algorithm
FM_8POINT = CV_FM_8POINT, //!< 8-point algorithm
FM_LMEDS = CV_FM_LMEDS, //!< least-median algorithm
FM_RANSAC = CV_FM_RANSAC //!< RANSAC algorithm
};
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS Mat findFundamentalMat( const Mat& points1, const Mat& points2,
CV_OUT vector<uchar>& mask, int method=FM_RANSAC,
double param1=3., double param2=0.99 );
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS_W Mat findFundamentalMat( const Mat& points1, const Mat& points2,
int method=FM_RANSAC,
double param1=3., double param2=0.99 );
It's on it OpenCV Calculate the fundamental matrix C++ Interface , Its internal implementation is still invoked C Code function , It is only a single encapsulation on the upper layer . Some documents on the Internet say const Mat& points1 and points2 These two parameters can be directly determined by vector Type as an incoming parameter , In fact, this is wrong . Direct use vector Pass on , There may be no error during compilation , But the runtime will crash directly . because cv::Mat The constructor of does not put Point2f Converted to two floating-point numbers and stored in Mat Of the two elements of , But still Point2f Stored in Mat In an element of , therefore findFundamentalMat As soon as I read this Mat Will go wrong .
So we'd better build it honestly Mat points1 and Mat points2. The dimension of the matrix can be 2xN, It can also be Nx2 Of , among N Is the number of characteristic points . Another thing to note is that the type of the matrix cannot be CV_64F, in other words findFundamentalMat Internal analysis points1 and points2 According to float Type to resolve , Set to CV_64F Will cause data reading failure , Program crash . It is better to set it to CV_32F. The following is calculated from the matched feature points F Code example of :
// vector<KeyPoint> m_LeftKey;
// vector<KeyPoint> m_RightKey;
// vector<DMatch> m_Matches;
// The above three variables have been calculated , They are the extracted key points and their matching , Let's calculate directly F
// Allocate space
int ptCount = (int)m_Matches.size();
Mat p1(ptCount, 2, CV_32F);
Mat p2(ptCount, 2, CV_32F);
// hold Keypoint Convert to Mat
Point2f pt;
for (int i=0; i<ptCount; i++)
{
pt = m_LeftKey[m_Matches[i].queryIdx].pt;
p1.at<float>(i, 0) = pt.x;
p1.at<float>(i, 1) = pt.y;
pt = m_RightKey[m_Matches[i].trainIdx].pt;
p2.at<float>(i, 0) = pt.x;
p2.at<float>(i, 1) = pt.y;
}
// use RANSAC Method to calculate F
// Mat m_Fundamental;
// The above variable is the basic matrix
// vector<uchar> m_RANSACStatus;
// The above variable has been defined , Used to store RANSAC Status of each point after
m_Fundamental = findFundamentalMat(p1, p2, m_RANSACStatus, FM_RANSAC);
// Calculate the number of wild points
int OutlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
if (m_RANSACStatus[i] == 0) // Status as 0 Indicates wild dot
{
OutlinerCount++;
}
}
// Calculate the interior point
// vector<Point2f> m_LeftInlier;
// vector<Point2f> m_RightInlier;
// vector<DMatch> m_InlierMatches;
// The above three variables are used to save the interior point and the matching relationship
int InlinerCount = ptCount - OutlinerCount;
m_InlierMatches.resize(InlinerCount);
m_LeftInlier.resize(InlinerCount);
m_RightInlier.resize(InlinerCount);
InlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
if (m_RANSACStatus[i] != 0)
{
m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0);
m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1);
m_RightInlier[InlinerCount].x = p2.at<float>(i, 0);
m_RightInlier[InlinerCount].y = p2.at<float>(i, 1);
m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
m_InlierMatches[InlinerCount].trainIdx = InlinerCount;
InlinerCount++;
}
}
// Convert the interior point to drawMatches Formats that can be used
vector<KeyPoint> key1(InlinerCount);
vector<KeyPoint> key2(InlinerCount);
KeyPoint::convert(m_LeftInlier, key1);
KeyPoint::convert(m_RightInlier, key2);
// Show calculation F Later interior point matching
// Mat m_matLeftImage;
// Mat m_matRightImage;
// The above two variables save the left and right images
Mat OutImage;
drawMatches(m_matLeftImage, key1, m_matRightImage, key2, m_InlierMatches, OutImage);
cvNamedWindow( "Match features", 1);
cvShowImage("Match features", &(IplImage(OutImage)));
cvWaitKey( 0 );
cvDestroyWindow( "Match features" );
————————————————
Copyright notice : This paper is about CSDN Blogger 「sunshine_guoqiang」 The original article of , follow CC 4.0 BY-SA Copyright agreement , For reprint, please attach the original source link and this statement .
Link to the original text :https://blog.csdn.net/Darlingqiang/article/details/79775542
边栏推荐
- 网上期货开户安全么?
- IDEA 官网插件地址
- [notice of the Association] notice on holding summer special teacher training in the field of artificial intelligence and Internet of things
- The first in China! EMQ joined Amazon cloud technology's "startup acceleration - global partner network program"
- Common errors and solutions of MySQL reading binlog logs
- 9.OpenFeign服务接口调用
- 网络传输是怎么工作的 -- 详解 OSI 模型
- im即时通讯开发之双进程守护保活实践
- 信息学奥赛一本通 1335:【例2-4】连通块
- Win10 LTSC 2021 wsappx CPU 占用高
猜你喜欢
Open source summer 2022 | opengauss project selected and announced
Hikvision Tools Manager海康威视工具大全(含SADP、录像容量计算等工具)百万安防从业者的实用工具
Keras deep learning practice (12) -- facial feature point detection
Vs code runs "yarn run dev" and reports "yarn": the file XXX cannot be loaded
Rxjs mergeMap 的使用场合
The data synchronization tool dataX has officially supported reading and writing tdengine
原创 | 2025实现“5个1”奋斗目标!解放动力全系自主非道路国四产品正式发布
芯动联科冲刺科创板:年营收1.7亿 北方电子院与中城创投是股东
Exporting coordinates of points in TXT format in ArcGIS
Using WebDAV instead of 445 port file share
随机推荐
SQL update批量更新
Rxjs mergeMap 的使用场合
云原生数据库:数据库的风口,你也可以起飞
Application of tdengine in monitoring of CNC machine tools
RANSAC的代码和原理
[elt.zip] openharmony paper Club - memory compression for data intensive applications
如何封裝調用一個庫
如何实现IM即时通讯“消息”列表卡顿优化
VSCode 建议你启用 gopls,它到底是个什么东东?
CDGA|交通行业做好数字化转型的核心是什么?
The incluxdb cluster function is no longer open source, and the tdengine cluster function is even better
Jinyuan's high-end IPO was terminated: it was planned to raise 750million Rushan assets and Liyang industrial investment were shareholders
laravel框架中 定时任务的实现
TIA博途_基于SCL语言制作模拟量输入输出全局库的具体方法
Differences between mongodb and MySQL
The first in China! EMQ joined Amazon cloud technology's "startup acceleration - global partner network program"
Win10 LTSC 2021 wsappx CPU usage high
[notice of the Association] notice on holding summer special teacher training in the field of artificial intelligence and Internet of things
在arcgis中以txt格式导出点的坐标
Core dynamic Lianke rushes to the scientific innovation board: with an annual revenue of 170million yuan, Beifang Electronics Institute and Zhongcheng venture capital are shareholders