当前位置:网站首页>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
边栏推荐
- Market status and development prospect forecast of global tetramethylammonium hydroxide developer industry in 2022
- Exporting coordinates of points in TXT format in ArcGIS
- im即时通讯开发之双进程守护保活实践
- 深度学习和神经网络的介绍
- Gartner聚焦中国低代码发展 UniPro如何践行“差异化”
- Open source summer 2022 | opengauss project selected and announced
- 技术分享 | kubernetes pod 简介
- Redis 原理 - String
- 可靠的分布式锁 RedLock 与 redisson 的实现
- 你知道 log4j2 各项配置的全部含义吗?带你了解 log4j2 的全部组件
猜你喜欢

脉脉热帖:为啥大厂都热衷于造轮子?

Blink SQL内置函数大全

SQL update batch update

Win10 LTSC 2021 wsappx CPU usage high

全面解析零知识证明:消解扩容难题 重新定义「隐私安全」

DFS and BFS simple principle

Win10 LTSC 2021 wsappx CPU 占用高

SQL update批量更新

China's Industrial Software Market Research Report is released, and SCADA and MES of force control enrich the ecology of domestic industrial software
![[elt.zip] openharmony paper Club - memory compression for data intensive applications](/img/b3/ab915f0338174cba1a003edc262a5d.png)
[elt.zip] openharmony paper Club - memory compression for data intensive applications
随机推荐
阿里巴巴的使命、愿景、核心价值观
使用logrotate对宝塔的网站日志进行自动切割
【云驻共创】高校数字化差旅建设“解决之道”
New Zhongda chongci scientific and Technological Innovation Board: annual revenue of 284million and proposed fund-raising of 557million
一位平凡毕业生的大学四年
[cloud based co creation] the "solution" of Digital Travel construction in Colleges and Universities
产学合作协同育人,麒麟软件携手南开大学合力完成《软件测试与维护》实践课程
Space calculation of information and innovation industry in 2022
Keras深度学习实战(12)——面部特征点检测
技术分享 | kubernetes pod 简介
银河麒麟V10系统激活
基于STM32F103ZET6库函数蜂鸣器实验
Comprehensively analyze the zero knowledge proof: resolve the expansion problem and redefine "privacy security"
Tdengine connector goes online Google Data Studio store
MySQL数据库登录和退出的两种方式
Cucumber自动化测试框架使用
Why migrate from opentsdb to tdengine
喜讯丨英方软件2022获得10项发明专利!
网上期货开户安全么?
Teach you how to install Oracle 19C on Windows 10 (detailed picture and text with step on pit Guide)