• 如何将图片进行竖直拼接(全景拼接)


    针对这个问题,我找遍了全网,都没有找到这个代码,都是在水平方向进行相应的拼接过程,自己也是看了一些代码,试图去修改一下,然后展示出来,但是修改的过程总是出现问题.

    重点来了,其实这个问题特别的容易解决,现在既然只能够实现水平拼接,那么将采集到的图片进行一个旋转,之后进行水平拼接,再将其旋转回去不就可以了吗.------是不是有种一语道破梦中人的感觉!(浪费了那么长时间去改代码,不如换一种思路去解决问题)

    首先是实现相应的图片旋转的过程,代码是如下所示:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. using namespace cv;
    6. int main()
    7. {
    8. Mat src = imread("C://Users//td//Desktop//相机图像大//7.jpg");
    9. Mat dst;
    10. Point center(src.cols / 2, src.rows / 2); //旋转中心
    11. double angle = -90.0; //角度
    12. double scale = 1.0; //缩放系数
    13. Mat rotMat = getRotationMatrix2D(center, angle, scale);
    14. warpAffine(src, dst, rotMat, src.size());
    15. imshow("src", src);
    16. Mat img_matchesOutput;
    17. resize(dst, img_matchesOutput, Size(1280, 720));
    18. imshow("MatchSIFT", img_matchesOutput);
    19. waitKey();
    20. }

    结果示意图:

    上面进行一个图片旋转的过程会产生相应的黑色区域,使得进行匹配的过程会出现问题,所以上面的过程是存在一定的问题的。

    1. #include
    2. using namespace cv;
    3. int main()
    4. {
    5. Mat src = imread("C:\\Users\\td\\Desktop\\相机图像大/t1.jpg", 1);
    6. Mat srcCopy = Mat(src.rows, src.cols, src.depth());
    7. transpose(src, srcCopy);
    8. //flip(srcCopy, srcCopy, 1); //rotate 270
    9. flip(srcCopy, srcCopy, 0); //rotate 90
    10. namedWindow("source Image", WINDOW_AUTOSIZE);
    11. imshow("source Image", src);
    12. namedWindow("Display Image", WINDOW_AUTOSIZE);
    13. imshow("Display Image", srcCopy);
    14. waitKey(0);
    15. return 0;
    16. }

    这个过程是不存在黑色区域的,结果图如下:这里应当注意的是,这里的旋转方向是沿着逆时针进行旋转的,想要改变旋转方向需要改变相应的一个旋转参数。

    旋转之前

    旋转之后

    后面是进行图片拼接的过程:图片拼接有好多方法,直接在网上进行一个调用就可以了。

    方法一:opencv库里面自带的一个Stitcher,(要配置OpenCV环境)调用的代码如下所示:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. int main()
    11. {
    12. //step 1. load img
    13. std::vector imgs;
    14. Mat img1 = imread("D:/image/img1.png", IMREAD_COLOR);
    15. Mat img2 = imread("D:/image/img2.png", IMREAD_COLOR);
    16. imgs.emplace_back(img1);
    17. imgs.emplace_back(img2);
    18. //进行拼接的函数 Stitcher
    19. Mat result;
    20. Stitcher stitcher = Stitcher::createDefault(false);
    21. Stitcher::Status status = stitcher.stitch(imgs, result);
    22. if (status != Stitcher::OK)
    23. {
    24. std::string error_msg = "Can't stitch images, error code = " +
    25. std::to_string(status);
    26. printff(error_msg);
    27. }
    28. else
    29. {
    30. imshow("result", result);//这里的图片如果要是过大是显示不全的,可以调用一个改变图片大小的函数
    31. }
    32. waitKey(0);
    33. return 0;
    34. }

    方法二:SIFT + RANSAC(这里需要说明一下配置环境,因为SIFT角点检测方法在OpenCV 3.x版本就申请了专利,因此,建议使用Opencv 2.9.0版本)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using namespace cv;
    10. using namespace std;
    11. int main()
    12. {
    13. initModule_nonfree();//初始化模块,使用SIFT或SURF时用到
    14. Ptr detector = FeatureDetector::create( "SURF" );//创建SIFT特征检测器,可改成SURF/ORB
    15. Ptr descriptor_extractor = DescriptorExtractor::create( "SURF" );//创建特征向量生成器,可改成SURF/ORB
    16. Ptr descriptor_matcher = DescriptorMatcher::create( "BruteForce" );//创建特征匹配器
    17. if( detector.empty() || descriptor_extractor.empty() )
    18. cout<<"fail to create detector!";
    19. Mat src1 = imread("C:\\Users\\td\\Desktop\\相机图像大\\直杆拼接\\Pic_1.bmp");
    20. //Mat src1 = imread("C:\\Users\\td\\Desktop\\相机图像大/t1.jpg");
    21. Mat img1 = Mat(src1.rows, src1.cols, src1.depth());
    22. transpose(src1, img1);
    23. flip(img1, img1, 3); //rotate 90
    24. Mat src2 = imread("C:\\Users\\td\\Desktop\\相机图像大\\直杆拼接\\Pic_2.bmp");
    25. //Mat src2 = imread("C:\\Users\\td\\Desktop\\相机图像大/t2.jpg");
    26. Mat img2 = Mat(src2.rows, src2.cols, src2.depth());
    27. transpose(src2, img2);
    28. flip(img2, img2, 3); //rotate 90
    29. //特征点检测
    30. double t = getTickCount();//当前滴答数
    31. vector m_LeftKey,m_RightKey;
    32. detector->detect( img1, m_LeftKey );//检测img1中的SIFT特征点,存储到m_LeftKey中
    33. detector->detect( img2, m_RightKey );
    34. cout<<"图像1特征点个数:"<size()<
    35. cout<<"图像2特征点个数:"<size()<
    36. //根据特征点计算特征描述子矩阵,即特征向量矩阵
    37. Mat descriptors1,descriptors2;
    38. descriptor_extractor->compute( img1, m_LeftKey, descriptors1 );
    39. descriptor_extractor->compute( img2, m_RightKey, descriptors2 );
    40. t = ((double)getTickCount() - t)/getTickFrequency();
    41. cout<<"SIFT算法用时:"<"秒"<
    42. cout<<"图像1特征描述矩阵大小:"<size()
    43. <<",特征向量个数:"<",维数:"<
    44. cout<<"图像2特征描述矩阵大小:"<size()
    45. <<",特征向量个数:"<",维数:"<
    46. //画出特征点
    47. Mat img_m_LeftKey,img_m_RightKey;
    48. drawKeypoints(img1,m_LeftKey,img_m_LeftKey,Scalar::all(-1),0);
    49. drawKeypoints(img2,m_RightKey,img_m_RightKey,Scalar::all(-1),0);
    50. //imshow("Src1",img_m_LeftKey);
    51. //imshow("Src2",img_m_RightKey);
    52. //特征匹配
    53. vector matches;//匹配结果
    54. descriptor_matcher->match( descriptors1, descriptors2, matches );//匹配两个图像的特征矩阵
    55. cout<<"Match个数:"<size()<
    56. //计算匹配结果中距离的最大和最小值
    57. //距离是指两个特征向量间的欧式距离,表明两个特征的差异,值越小表明两个特征点越接近
    58. double max_dist = 0;
    59. double min_dist = 100;
    60. for(int i=0; isize(); i++)
    61. {
    62. double dist = matches[i].distance;
    63. if(dist < min_dist) min_dist = dist;
    64. if(dist > max_dist) max_dist = dist;
    65. }
    66. cout<<"最大距离:"<
    67. cout<<"最小距离:"<
    68. //筛选出较好的匹配点
    69. vector goodMatches;
    70. for(int i=0; isize(); i++)
    71. {
    72. if(matches[i].distance < 0.2 * max_dist)
    73. {
    74. goodMatches.push_back(matches[i]);
    75. }
    76. }
    77. cout<<"goodMatch个数:"<size()<
    78. //画出匹配结果
    79. Mat img_matches;
    80. //红色连接的是匹配的特征点对,绿色是未匹配的特征点
    81. drawMatches(img1,m_LeftKey,img2,m_RightKey,goodMatches,img_matches,
    82. Scalar::all(-1)/*CV_RGB(255,0,0)*/,CV_RGB(0,255,0),Mat(),2);
    83. Mat img_matchesOutput2;
    84. resize(img_matches, img_matchesOutput2, Size(1280, 720));
    85. imshow("MatchSIFT", img_matchesOutput2);
    86. IplImage result=img_matches;
    87. waitKey(0);
    88. //RANSAC匹配过程
    89. vector m_Matches=goodMatches;
    90. // 分配空间
    91. int ptCount = (int)m_Matches.size();
    92. Mat p1(ptCount, 2, CV_32F);
    93. Mat p2(ptCount, 2, CV_32F);
    94. // 把Keypoint转换为Mat
    95. Point2f pt;
    96. for (int i=0; i
    97. {
    98. pt = m_LeftKey[m_Matches[i].queryIdx].pt;
    99. p1.at<float>(i, 0) = pt.x;
    100. p1.at<float>(i, 1) = pt.y;
    101. pt = m_RightKey[m_Matches[i].trainIdx].pt;
    102. p2.at<float>(i, 0) = pt.x;
    103. p2.at<float>(i, 1) = pt.y;
    104. }
    105. // 用RANSAC方法计算F
    106. Mat m_Fundamental;
    107. vector m_RANSACStatus; // 这个变量用于存储RANSAC后每个点的状态
    108. findFundamentalMat(p1, p2, m_RANSACStatus, FM_RANSAC);
    109. // 计算野点个数
    110. int OutlinerCount = 0;
    111. for (int i=0; i
    112. {
    113. if (m_RANSACStatus[i] == 0) // 状态为0表示野点
    114. {
    115. OutlinerCount++;
    116. }
    117. }
    118. int InlinerCount = ptCount - OutlinerCount; // 计算内点
    119. cout<<"内点数为:"<
    120. // 这三个变量用于保存内点和匹配关系
    121. vector m_LeftInlier;
    122. vector m_RightInlier;
    123. vector m_InlierMatches;
    124. m_InlierMatches.resize(InlinerCount);
    125. m_LeftInlier.resize(InlinerCount);
    126. m_RightInlier.resize(InlinerCount);
    127. InlinerCount=0;
    128. float inlier_minRx=img1.cols; //用于存储内点中右图最小横坐标,以便后续融合
    129. for (int i=0; i
    130. {
    131. if (m_RANSACStatus[i] != 0)
    132. {
    133. m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0);
    134. m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1);
    135. m_RightInlier[InlinerCount].x = p2.at<float>(i, 0);
    136. m_RightInlier[InlinerCount].y = p2.at<float>(i, 1);
    137. m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
    138. m_InlierMatches[InlinerCount].trainIdx = InlinerCount;
    139. if(m_RightInlier[InlinerCount].x//存储内点中右图最小横坐标
    140. InlinerCount++;
    141. }
    142. }
    143. // 把内点转换为drawMatches可以使用的格式
    144. vector key1(InlinerCount);
    145. vector key2(InlinerCount);
    146. KeyPoint::convert(m_LeftInlier, key1);
    147. KeyPoint::convert(m_RightInlier, key2);
    148. // 显示计算F过后的内点匹配
    149. Mat OutImage;
    150. drawMatches(img1, key1, img2, key2, m_InlierMatches, OutImage);
    151. //cvNamedWindow( "Match features", 1);
    152. //cvShowImage("Match features", &IplImage(OutImage));
    153. waitKey(0);
    154. cvDestroyAllWindows();
    155. //矩阵H用以存储RANSAC得到的单应矩阵
    156. Mat H = findHomography( m_LeftInlier, m_RightInlier, RANSAC );
    157. //存储左图四角,及其变换到右图位置
    158. std::vector obj_corners(4);
    159. obj_corners[0] = Point(0,0); obj_corners[1] = Point( img1.cols, 0 );
    160. obj_corners[2] = Point( img1.cols, img1.rows ); obj_corners[3] = Point( 0, img1.rows );
    161. std::vector scene_corners(4);
    162. perspectiveTransform( obj_corners, scene_corners, H);
    163. //画出变换后图像位置
    164. Point2f offset( (float)img1.cols, 0);
    165. line( OutImage, scene_corners[0]+offset, scene_corners[1]+offset, Scalar( 0, 255, 0), 4 );
    166. line( OutImage, scene_corners[1]+offset, scene_corners[2]+offset, Scalar( 0, 255, 0), 4 );
    167. line( OutImage, scene_corners[2]+offset, scene_corners[3]+offset, Scalar( 0, 255, 0), 4 );
    168. line( OutImage, scene_corners[3]+offset, scene_corners[0]+offset, Scalar( 0, 255, 0), 4 );
    169. imshow( "Good Matches & Object detection", OutImage );
    170. Mat img_matchesOutput3;
    171. resize(OutImage, img_matchesOutput3, Size(1280, 720));
    172. imshow("Good Matches & Object detection", img_matchesOutput3);
    173. /* while(1)
    174. {
    175. if(waitKey(100)==19) cvSaveImage("E:\\warp_position.jpg", &IplImage(OutImage));
    176. if(waitKey(100)==27) break;
    177. } //按esc继续,ctl+s保存图像
    178. */
    179. int drift = scene_corners[1].x; //储存偏移量
    180. //新建一个矩阵存储配准后四角的位置
    181. int width = int(max(abs(scene_corners[1].x), abs(scene_corners[2].x)));
    182. int height= img1.rows; //或者:int height = int(max(abs(scene_corners[2].y), abs(scene_corners[3].y)));
    183. float origin_x=0,origin_y=0;
    184. if(scene_corners[0].x<0) {
    185. if (scene_corners[3].x<0) origin_x+=min(scene_corners[0].x,scene_corners[3].x);
    186. else origin_x+=scene_corners[0].x;}
    187. width-=int(origin_x);
    188. if(scene_corners[0].y<0) {
    189. if (scene_corners[1].y) origin_y+=min(scene_corners[0].y,scene_corners[1].y);
    190. else origin_y+=scene_corners[0].y;}
    191. //可选:height-=int(origin_y);
    192. Mat imageturn=Mat::zeros(width,height,img1.type());
    193. //获取新的变换矩阵,使图像完整显示
    194. for (int i=0;i<4;i++) {scene_corners[i].x -= origin_x; } //可选:scene_corners[i].y -= (float)origin_y; }
    195. Mat H1=getPerspectiveTransform(obj_corners, scene_corners);
    196. //进行图像变换,显示效果
    197. warpPerspective(img1,imageturn,H1,Size(width,height));
    198. Mat img_matchesOutput4;
    199. resize(imageturn, img_matchesOutput4, Size(1280, 720));
    200. imshow("image_Perspective", img_matchesOutput4);
    201. waitKey(0);
    202. //图像融合
    203. int width_ol=width-int(inlier_minRx-origin_x);
    204. int start_x=int(inlier_minRx-origin_x);
    205. cout<<"width: "<
    206. cout<<"img1.width: "<
    207. cout<<"start_x: "<
    208. cout<<"width_ol: "<
    209. uchar* ptr=imageturn.data;
    210. double alpha=0, beta=1;
    211. for (int row=0;row
    212. ptr=imageturn.data+row*imageturn.step+(start_x)*imageturn.elemSize();
    213. for(int col=0;col
    214. {
    215. uchar* ptr_c1=ptr+imageturn.elemSize1(); uchar* ptr_c2=ptr_c1+imageturn.elemSize1();
    216. uchar* ptr2=img2.data+row*img2.step+(col+int(inlier_minRx))*img2.elemSize();
    217. uchar* ptr2_c1=ptr2+img2.elemSize1(); uchar* ptr2_c2=ptr2_c1+img2.elemSize1();
    218. alpha=double(col)/double(width_ol); beta=1-alpha;
    219. if (*ptr==0&&*ptr_c1==0&&*ptr_c2==0) {
    220. *ptr=(*ptr2);
    221. *ptr_c1=(*ptr2_c1);
    222. *ptr_c2=(*ptr2_c2);
    223. }
    224. *ptr=(*ptr)*beta+(*ptr2)*alpha;
    225. *ptr_c1=(*ptr_c1)*beta+(*ptr2_c1)*alpha;
    226. *ptr_c2=(*ptr_c2)*beta+(*ptr2_c2)*alpha;
    227. ptr+=imageturn.elemSize();
    228. } }
    229. //imshow("image_overlap", imageturn);
    230. //waitKey(0);
    231. Mat img_result=Mat::zeros(height,width+img2.cols-drift,img1.type());
    232. uchar* ptr_r=imageturn.data;
    233. for (int row=0;row
    234. ptr_r=img_result.data+row*img_result.step;
    235. for(int col=0;col
    236. {
    237. uchar* ptr_rc1=ptr_r+imageturn.elemSize1(); uchar* ptr_rc2=ptr_rc1+imageturn.elemSize1();
    238. uchar* ptr=imageturn.data+row*imageturn.step+col*imageturn.elemSize();
    239. uchar* ptr_c1=ptr+imageturn.elemSize1(); uchar* ptr_c2=ptr_c1+imageturn.elemSize1();
    240. *ptr_r=*ptr;
    241. *ptr_rc1=*ptr_c1;
    242. *ptr_rc2=*ptr_c2;
    243. ptr_r+=img_result.elemSize();
    244. }
    245. ptr_r=img_result.data+row*img_result.step+imageturn.cols*img_result.elemSize();
    246. for(int col=imageturn.cols;col
    247. {
    248. uchar* ptr_rc1=ptr_r+imageturn.elemSize1(); uchar* ptr_rc2=ptr_rc1+imageturn.elemSize1();
    249. uchar* ptr2=img2.data+row*img2.step+(col-imageturn.cols+drift)*img2.elemSize();
    250. uchar* ptr2_c1=ptr2+img2.elemSize1(); uchar* ptr2_c2=ptr2_c1+img2.elemSize1();
    251. *ptr_r=*ptr2;
    252. *ptr_rc1=*ptr2_c1;
    253. *ptr_rc2=*ptr2_c2;
    254. ptr_r+=img_result.elemSize();
    255. }
    256. }
    257. Mat img_matchesOutput5;
    258. resize(img_result, img_matchesOutput5, Size(1280, 720));
    259. imshow("image_result", img_matchesOutput5);
    260. /*
    261. while(1)
    262. {
    263. if(waitKey(100)==19) cvSaveImage("E:\\final_result.jpg", &IplImage(img_result));
    264. if(waitKey(100)==27) break; //按esc退出,ctl+s保存图像
    265. }
    266. */
    267. return 0;
    268. }

    当然是还有其他的方法,SIFT毕竟检测速度并不是很快,其余的方法自行研究。

  • 相关阅读:
    Notexpress 从另一个文档粘贴过来引文出错的问题
    2022年SQL大厂高频实战面试题(详细解析)
    Python 读写 Excel 文件
    C++课堂整理--第一章内容
    十年老程序员分享13个最常用的Python深度学习库和介绍,赶紧收藏码住!
    【C语言简单实现数据结构】排序之交换排序和归并排序
    vue处理边界情况
    【数仓基础(一)】基础概念:数据仓库【用于决策的数据集合】的概念、建立数据仓库的原因与好处
    2169 -- 最大公约数题解
    linux 修改用户的密码
  • 原文地址:https://blog.csdn.net/m0_47489229/article/details/126890476