OpenCV图像全景拼接-黑边处理

2022-04-24  本文已影响0人  JS_XiaoShu

OpenCV为我们提供了图像拼接的工具类:Stitcher,用过的同学都知道,只有拼接后的Mat,没有拼接角度等数据,还需要自己处理拼接后的黑边。

参考资料(python):https://blog.csdn.net/weixin_41611054/article/details/120340485

上面链接的效果已经很不错了,无奈我需要在Android端实现(C++)未找到相关资料接下来是自己处理的思路

拼接:

vector<cv::Mat>imgs;
imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f0.jpg"));
imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f1.jpg"));
imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f2.jpg"));
imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f3.jpg"));
cv::Mat pano;
cv::Ptr<cv::Stitcher> stitcher = cv::Stitcher::create(cv::Stitcher::PANORAMA);
cv::Stitcher::Status status = stitcher->stitch(imgs, pano);//拼接 

效果:


WX20220424-153111@2x.png

可见效果是个不规则的图形,经过多次尝试无果后,我用了最笨的方法---计算像素点来判断黑边,我的思路是从四个角往里面算,找到第一个不是黑色的为止

转成黑白图片:

cv::Mat stitched;cv::copyMakeBorder(pano, stitched, 0, 0, 0, 0, cv::BORDER_CONSTANT, (0, 0, 0));
cv::Mat gray;cv::cvtColor(stitched, gray, cv::COLOR_BGR2GRAY);
cv::Mat thresh = pano.clone();
cv::threshold(gray, thresh, 0, 255, cv::THRESH_BINARY);
WX20220424-153552@2x.png

四个角往里面找思路如图:


WX20220424-153727@2x.png

找左上角:

cv::Point2f leftTopPt;
    for (int i = 1; i < thresh.rows; ++i) {
        char pixel = thresh.at<char>(i, i);
        if (pixel != 0) { //不等于黑色
            leftTopPt.x = i;
            leftTopPt.y = i;
            break;
        }
    }

    cv::Scalar color(10, 10, 255);
    // 用黑白图片计算的结果,在原图画个小圆点(黑白图上画不够明显)
    cv::circle(pano, leftTopPt, 5, color);
WX20220424-155635@2x.png

(画红点和先是为了方便自己看到裁剪的区域)
以此类推,找出左上角、右上角、左下角、右下角的点,连接点画线:

    cv::Point2f leftTopPt;
    for (int i = 1; i < thresh.rows; ++i) {
        char pixel = thresh.at<char>(i, i);
        if (pixel != 0) { //不等于黑色
            leftTopPt.x = i;
            leftTopPt.y = i;
            break;
        }
    }

    cv::Point2f rightTopPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = thresh.cols - i;
        int y = i;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            rightTopPt.x = x;
            rightTopPt.y = y;
            break;
        }
    }

    cv::Point2f leftBottomPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = i;
        int y = thresh.rows - i;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            leftBottomPt.x = x;
            leftBottomPt.y = y;
            break;
        }
    }

    cv::Point2f rightBottomPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = thresh.cols - i;
        int y = thresh.rows - i;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            rightBottomPt.x = x;
            rightBottomPt.y = y;
            break;
        }
    }

    cv::Scalar color(10, 10, 255);
    // 用黑白图片计算的结果,在原图画个小圆点(黑白图上画不够明显)
    cv::circle(pano, leftTopPt, 5, color, -1);
    cv::circle(pano, rightTopPt, 5, color, -1);
    cv::circle(pano, leftBottomPt, 5, color, -1);
    cv::circle(pano, rightBottomPt, 5, color, -1);

    cv::line(pano, leftTopPt, rightTopPt, color);
    cv::line(pano, leftTopPt, leftBottomPt, color);
    cv::line(pano, rightTopPt, rightBottomPt, color);
    cv::line(pano, leftBottomPt, rightBottomPt, color);
WX20220424-160257@2x.png

是不是有亿点点感觉了?明显按照这个线裁剪是不行的,线不是直,接下来处理四边的线,以小的为准:

        int topMaxY = max(leftTopPt.y , rightTopPt.y);
        int leftMaxX = max(leftTopPt.x , leftBottomPt.x);
        int rightMinX = min(rightTopPt.x , rightBottomPt.x);
        int bottomMinY = min(leftBottomPt.y , rightBottomPt.y);
        leftTopPt.y = topMaxY;
        rightTopPt.y = topMaxY;
        leftTopPt.x = leftMaxX;
        leftBottomPt.x = leftMaxX;
        rightTopPt.x = rightMinX;
        rightBottomPt.x = rightMinX;
        leftBottomPt.y = bottomMinY;
        rightBottomPt.y = bottomMinY;
WX20220424-160853@2x.png

然后裁剪且保存,完整代码:

vector<cv::Mat> imgs;
    imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f0.jpg"));
    imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f1.jpg"));
    imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f2.jpg"));
    imgs.push_back(cv::imread("/Users/xiaoshumac/Documents/st/f3.jpg"));
    cv::Mat pano;
    cv::Ptr<cv::Stitcher> stitcher = cv::Stitcher::create(cv::Stitcher::PANORAMA);
    cv::Stitcher::Status status = stitcher->stitch(imgs, pano);//拼接

    cv::Mat stitched;
    cv::copyMakeBorder(pano, stitched, 0, 0, 0, 0, cv::BORDER_CONSTANT, (0, 0, 0));

    cv::Mat gray;
    cv::cvtColor(stitched, gray, cv::COLOR_BGR2GRAY);
    cv::Mat thresh = pano.clone();
    cv::threshold(gray, thresh, 0, 255, cv::THRESH_BINARY);

    cv::Point2f leftTopPt;
    for (int i = 1; i < thresh.rows; ++i) {
        char pixel = thresh.at<char>(i, i);
        if (pixel != 0) { //不等于黑色
            leftTopPt.x = I;
            leftTopPt.y = I;
            break;
        }
    }

    cv::Point2f rightTopPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = thresh.cols - I;
        int y = I;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            rightTopPt.x = x;
            rightTopPt.y = y;
            break;
        }
    }

    cv::Point2f leftBottomPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = I;
        int y = thresh.rows - I;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            leftBottomPt.x = x;
            leftBottomPt.y = y;
            break;
        }
    }

    cv::Point2f rightBottomPt;
    for (int i = 1; i < thresh.rows; ++i) {
        int x = thresh.cols - I;
        int y = thresh.rows - I;
        char pixel = thresh.at<char>(y, x);
        if (pixel != 0) {
            rightBottomPt.x = x;
            rightBottomPt.y = y;
            break;
        }
    }

    int topMaxY = max(leftTopPt.y , rightTopPt.y);
    int leftMaxX = max(leftTopPt.x , leftBottomPt.x);
    int rightMinX = min(rightTopPt.x , rightBottomPt.x);
    int bottomMinY = min(leftBottomPt.y , rightBottomPt.y);
    leftTopPt.y = topMaxY;
    rightTopPt.y = topMaxY;
    leftTopPt.x = leftMaxX;
    leftBottomPt.x = leftMaxX;
    rightTopPt.x = rightMinX;
    rightBottomPt.x = rightMinX;
    leftBottomPt.y = bottomMinY;
    rightBottomPt.y = bottomMinY;
//
//    cv::Scalar color(10, 10, 255);
//    // 用黑白图片计算的结果,在原图画个小圆点(黑白图上画不够明显)
//    cv::circle(pano, leftTopPt, 5, color, -1);
//    cv::circle(pano, rightTopPt, 5, color, -1);
//    cv::circle(pano, leftBottomPt, 5, color, -1);
//    cv::circle(pano, rightBottomPt, 5, color, -1);
//
//    cv::line(pano, leftTopPt, rightTopPt, color);
//    cv::line(pano, leftTopPt, leftBottomPt, color);
//    cv::line(pano, rightTopPt, rightBottomPt, color);
//    cv::line(pano, leftBottomPt, rightBottomPt, color);

    // 裁剪
    cv::Mat tempMat = pano(cv::Rect(leftTopPt.x, leftTopPt.y, rightBottomPt.x - leftTopPt.x, rightBottomPt.y - leftTopPt.y));
    // 保存(画点画线仅为了方便测试,保存注释画的代码)
    cv::imwrite("/Users/xiaoshumac/Documents/st/tempMat.jpg", tempMat);
tempMat.jpg

这就完事了?并没有,这只是针对上图这类效果有效,对于9宫格裁剪或者拼接后面有大面积黑边仅靠上面代码还无法完美处理,有需要的同学我再更新,暂时写到这了
因为是计算黑色点作为边界处理,所以上面代码对于夜景拼接处理不好,还有就是计算像素点是属于”笨“方法,有更优的思路麻烦大佬告知下
关于耗时:虽然上面用了很多for循环,好在OpenCV取像素点是非常快的,上面图像在我的电脑:500ms左右, 小米9也在2秒以内

---2022/04/26---
夜景拼接效果也处理好了,需要的同学再更新

上一篇下一篇

猜你喜欢

热点阅读