2017-01-04 16:07:25 Bin5243 阅读数 659
  • OpenCV4 图像处理与视频分析实战教程

    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API 与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到视频分析,涵盖了计算机视觉与OpenCV4 中主要模块的相关知识点,穿插大量工程编程技巧与知识点与案例,全部课程的PPT课件与源码均可以下载。部分课程内容运行截图:

    1686 人正在学习 去看看 贾志刚
先空着
2017-11-09 14:36:37 wc781708249 阅读数 1987
  • OpenCV4 图像处理与视频分析实战教程

    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API 与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到视频分析,涵盖了计算机视觉与OpenCV4 中主要模块的相关知识点,穿插大量工程编程技巧与知识点与案例,全部课程的PPT课件与源码均可以下载。部分课程内容运行截图:

    1686 人正在学习 去看看 贾志刚

参考:http://blog.csdn.net/wc781708249/article/details/78485415


1、python版

说明:
数据类型:原类型16bit 4波段 (CV_16UC4),转成32float(CV_32FC4)处理,处理完成后保存成CV_16UC4


1.1绘图与写文本

# 画图
dst=cv2.line(img,(0,0),(2000,2000),(255,0,0),5,cv2.LINE_AA) # 画线
dst = cv2.rectangle(dst,(384,0),(510,128),(0,255,0),3) # 画矩形
dst = cv2.circle(dst,(447,63), 63, (0,0,255), -1) # -1 表示填充
dst = cv2.ellipse(dst,(256,256),(100,50),0,0,180,255,-1)  # 这里的255 相当于(255,0,0)

# 画多边形
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
dst = cv2.polylines(dst,[pts],True,(0,255,255),3)

# 写入文字
font = cv2.FONT_HERSHEY_SIMPLEX # 字体
cv2.putText(dst,'OpenCV',(10,256), font, 4,(255,255,255),2,cv2.LINE_AA)
# ----------------------------------------------------------

1.2 分割和合并图像通道

# 分割和合并图像通道
b,g,r = cv2.split(img)
img = cv2.merge((r,g,b)) # BGR转成RGB 针对3个波段的
# or
b=img[:,:,0]
g=img[:,:,1]
r=img[:,:,2]
img2 = cv2.merge((r,g,b))

# 多于3个波段
n_data=cv2.split(img)
dst=cv2.merge((n_data[0],n_data[1],n_data[2])) # 这里只合并前3个波段

1.3 制作边框图像

# 制作边框图像
dst=cv2.copyMakeBorder(img,100,100,100,100,cv2.BORDER_CONSTANT,value=(255,0,0))


replicate = cv2.copyMakeBorder(img,10,10,10,10,cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img,10,10,10,10,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img,10,10,10,10,cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img,10,10,10,10,cv2.BORDER_WRAP)
constant= cv2.copyMakeBorder(img,10,10,10,10,cv2.BORDER_CONSTANT,value=(255,0,0))

1.4 访问

# 访问和修改像素值
px = img[100,100] # 访问像素值
img[100,100] = [255,255,255] # 修改像素值

img.item(10,10,2) # 访问像素值
img.itemset((10,10,2),100) # 修改像素值

# 访问图像属性
img.shape # [h,w,c]
img.size # h*w*c
img.dtype # float32
type(img) # numpy array

# 图像ROI
ball = img[280:340, 330:390]
img[273:333, 100:160] = ball

# 类型转换
img.astype(np.float32)

# 计算耗时
e1 = cv2.getTickCount()
# your code execution
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()

1.5 图像算术运算

# 图像加法
dst=cv2.add(img,img)

# 图像混合
dst = cv2.addWeighted(img,0.7,img,0.3,0)

# 按位操作
cv2.bitwise_not()
cv2.bitwise_and()
cv2.bitwise_or()
cv2.bitwise_xor()

1.6 阈值处理

# 简单的阈值
ret,dst = cv2.threshold(img,np.mean(img)*1.5,np.max(img),cv2.THRESH_BINARY)
cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO_INV

# 自适应阈值
dst = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
            cv2.THRESH_BINARY,11,2)

# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

1.7图像的几何变换

# 缩放
dst = cv2.resize(img,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC) # h,w放大2倍

height, width = img.shape[:2]
dst = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC) # h,w放大2倍

# 平移
rows,cols = img.shape[:2]
M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))

# 旋转
M = cv2.getRotationMatrix2D((cols/2,rows/2),90,1) # 逆时针旋转90度
dst = cv2.warpAffine(img,M,(cols,rows))

# 仿射变换
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])
M = cv2.getAffineTransform(pts1,pts2)
dst = cv2.warpAffine(img,M,(cols,rows))

# 透视转换
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(img,M,(300,300))


# 重新映射
map_x=np.zeros(img.shape[:2],np.float32)
map_y=np.zeros(img.shape[:2],np.float32)

for j in range(img.shape[0]):
    for i in range(img.shape[1]):

        # 将图片缩小一半,并将其显示在中间:
        if i>img.shape[1]*0.25 and i<img.shape[1]*0.75 and j>img.shape[0]*0.25 and j<img.shape[0]*0.75:
            map_x[j,i]=2*(i-img.shape[1]*0.25)+0.5
            map_y[j,i]=2*(j-img.shape[1]*0.25)+0.5
        else:
            map_x[j,i]=0
            map_y[j,i]=0

        # 上下翻转图像
        map_x[j,i]=i
        map_y[j,i]=img.shape[0]-j

        # # 左右对调
        map_x[j,i]=img.shape[1]-i
        map_y[j,i]=j

        # # 结合上下翻转和左右对调
        map_x[j,i] = img.shape[1] - i
        map_y[j,i] = img.shape[0] - j

dst=cv2.remap(img, map_x, map_y, cv2.INTER_LINEAR,None, cv2.BORDER_CONSTANT, (0, 0, 0))

1.8 图像平滑

# 二维卷积
kernel = np.ones((5,5),np.float32)/25
dst = cv2.filter2D(img,-1,kernel)

# 均值滤波
dst=cv2.blur(img,(5,5))

# 高斯滤波
dst=cv2.GaussianBlur(img,(5,5),0)

# 中值滤波
dst=cv2.medianBlur(img,5)

# 双边过滤
dst=cv2.bilateralFilter(img,9,75,75)

1.9 形态操作

# 腐蚀
kernel = np.ones((5,5),np.uint8)
dst = cv2.erode(img,kernel,iterations = 1)

# 膨胀
kernel = np.ones((7,7),np.uint8)  # 内核
erosion = cv2.erode(img,kernel,iterations = 1)  # 腐蚀去除噪声
dst = cv2.dilate(erosion,kernel,iterations = 1) # 膨胀恢复原形状

2、C++版

说明:
数据类型:原类型16bit 4波段 (CV_16UC4)(不转成32float(CV_32FC4)处理 ,转float32处理完保存图像会出现各种问题),直接使用原格式处理,处理完成后保存成CV_16UC4


2.1绘图与写文本

//////////////////////////////////////////////////////
    /*画图*/
    //Mat dst;//输出Mat
    line(img, Point(0, 200), Point(1000, 1000), Scalar(0, 0, 0), 2, LINE_8);//画线
    circle(img, Point(200, 200), 30, Scalar(0, 0, 255), FILLED, LINE_8);
    ellipse(img,Point(100, 100),Size(50, 50),90,0,360,Scalar(255, 0, 0),2,LINE_AA);
    rectangle(img,Point(0, 7 * w / 8),Point(w, w),Scalar(0, 255, 255),FILLED,LINE_8);
    fillPoly(img,ppt,npt,1,Scalar(255, 255, 255), LINE_8);

    putText(img, "Testing text rendering", org, rng.uniform(0, 8),
        rng.uniform(0, 100)*0.05 + 0.1, randomColor(rng), rng.uniform(1, 10), LINE_8);

    /////////////////////////////////////////////////////

2.2 分割和合并图像通道

    std::vector<cv::Mat> imgMat(St.nbands);
    std::vector<cv::Mat> tempMat(St.nbands);
    cv::split(img, imgMat); //分离通道
    tempMat.at(0) = (imgMat.at(2));//BGR-->RGB
    tempMat.at(1) = (imgMat.at(1));
    tempMat.at(2) = (imgMat.at(0));
    Mat img2;
    cv::merge(tempMat, img2);// 合并通道
    imgMat.clear();
    tempMat.clear();

附加类型转换

 img.convertTo(dst, CV_32FC4); //转成32F类型

2.3 制作边框图像

Mat dst;
    int top, bottom, left, right;
    top = (int)(0.05*img.rows); bottom = (int)(0.05*img.rows);
    left = (int)(0.05*img.cols); right = (int)(0.05*img.cols);

    copyMakeBorder(img, dst, 50, 50, 50, 50, BORDER_CONSTANT, Scalar(255, 0, 0));

2.4 访问

    //img类型为Mat
    //ROI
    Mat D(img, Rect(10, 10, 100, 100)); // using a rectangle
    Mat E = img(Range::all(), Range(1, 3)); // using row and column boundaries

    //复制
    Mat F = img.clone();
    Mat G;
    img.copyTo(G);

    img.channels(); //通道数
    img.rows;//行数 ysize
    img.cols;//列数 xsize
    img.data;//获取img中所有像素值

    CV_Assert(img.depth() == CV_8U);//判断数据类型


    //访问像素
    img.at<Vec3b>(y, x);
    img.at<Vec3b>(Point(x, y));
    uchar* p = img.ptr();
    p[10];

    //修改像素值
    img.at<uchar>(y, x) = 128;

    Vec3f intensity = img.at<Vec3f>(y, x);
    float blue = intensity.val[0];
    float green = intensity.val[1];
    float red = intensity.val[2];

    //计算耗时
    double t = (double)getTickCount();
    // do something ...
    t = ((double)getTickCount() - t) / getTickFrequency();
    cout << "Times passed in seconds: " << t << endl;


    //类型数据转换
    img.convertTo(img,CV_32FC4);

2.5 图像算术运算

beta = (1.0 - alpha);
addWeighted(src1, alpha, src2, beta, 0.0, dst);

2.6 阈值处理

2019-05-29 12:11:35 WHU_StudentZhong 阅读数 414
  • OpenCV4 图像处理与视频分析实战教程

    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API 与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到视频分析,涵盖了计算机视觉与OpenCV4 中主要模块的相关知识点,穿插大量工程编程技巧与知识点与案例,全部课程的PPT课件与源码均可以下载。部分课程内容运行截图:

    1686 人正在学习 去看看 贾志刚

(环境:VS2017+OPENCV4.0)

K值聚类算法并不复杂,这里不多介绍,直接上代码。

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>
using namespace std;
using namespace cv;

struct SPoint {//点和点的类别
    double v1;
    double v2;
    double v3;
    int id;
};
void Color(SPoint* pt, int id) {//给不同类别赋予颜色,可以自己加
    switch (pt->id) {
    case 0:
        pt->v1 = 0;
        pt->v2 = 255;
        pt->v3 = 170;
        break;
    case 1:
        pt->v1 = 255;
        pt->v2 = 0;
        pt->v3 = 0;
        break;
    case 2:
        pt->v1 = 0;
        pt->v2 = 255;
        pt->v3 = 0;
        break;
    case 3:
        pt->v1 = 0;
        pt->v2 = 0;
        pt->v3 = 255;
        break;
    case 4:
        pt->v1 = 255;
        pt->v2 = 170;
        pt->v3 = 200;
        break;
    case 5:
        pt->v1 = 100;
        pt->v2 = 100;
        pt->v3 = 100;
        break;
    case 6:
        pt->v1 = 200;
        pt->v2 = 200;
        pt->v3 = 200;
        break;
    }
}
double Norm2(SPoint p1, SPoint p2) {//距离的平方
    return (pow((p1.v1 - p2.v1), 2) + pow((p1.v2 - p2.v2), 2) + pow((p1.v3 - p2.v3), 2));
    //return (p1.v1 - p2.v1)*(p1.v1 - p2.v1) + (p1.v2 - p2.v2)*(p1.v2 - p2.v2) + (p1.v3 - p2.v3)*(p1.v3 - p2.v3);
}
Mat Kmean(Mat src, int species, int iterations) {//原图像,种类数,迭代次数
    Mat K = src;
    int  rows = src.rows, cols = src.cols;//行,列
    SPoint* Points = new SPoint[rows*cols];
    SPoint* CenterPt1 = new SPoint[species];
    SPoint* CenterPt2 = new SPoint[species];
    int* num = new int[species];
    double* sum1 = new double[species];
    double* sum2 = new double[species];
    double* sum3 = new double[species];
    for (int x = 0; x < cols; x++) {//把图像信息读入
        for (int y = 0; y < rows; y++) {
            Points[x + y * cols].v1 = src.at<Vec3b>(y, x)[0]; // blue
            Points[x + y * cols].v2 = src.at<Vec3b>(y, x)[1]; // green
            Points[x + y * cols].v3 = src.at<Vec3b>(y, x)[2]; // red
            Points[x + y * cols].id = 0;
        }
    }
    for (int i = 0; i < species; i++) {//初始化中心
        CenterPt1[i] = Points[i * 10];
        CenterPt1[i].id = i;
        CenterPt2[i] = Points[i * 10];
        CenterPt2[i].id = i;
        num[i] = 0;
        sum1[i] = sum2[i] = sum3[i] = 0;
    }
    double min = 20000000;
    double error = 0;
    while (iterations > 0) {
        for (int x = 0; x < cols; x++) {//把图像信息读入
            for (int y = 0; y < rows; y++) {
                for (int i = 0; i < species; i++) {
                    int id = CenterPt1[i].id;
                    if (Norm2(Points[x + y * cols], CenterPt1[i]) < min) {
                        Points[x + y * cols].id = id;
                        min = Norm2(Points[x + y * cols], CenterPt1[i]);
                        num[id]++;
                        sum1[id] += Points[x + y * cols].v1;
                        sum2[id] += Points[x + y * cols].v2;
                        sum3[id] += Points[x + y * cols].v3;
                    }
                }
                min = 20000000;
            }
        }
        iterations--;

        for (int i = 0; i < species; i++) {
            CenterPt2[i].v1 = sum1[i] / num[i];
            CenterPt2[i].v2 = sum2[i] / num[i];
            CenterPt2[i].v3 = sum3[i] / num[i];
            error += pow((CenterPt2[i].v1 - CenterPt1[i].v1), 2) + pow((CenterPt2[i].v2 - CenterPt1[i].v2), 2) + pow((CenterPt2[i].v3 - CenterPt1[i].v3), 2);
        }
        if (error < 0.0001)
            break;
        for (int i = 0; i < species; i++) {
            CenterPt1[i].v1 = CenterPt2[i].v1;
            CenterPt1[i].v2 = CenterPt2[i].v2;
            CenterPt1[i].v3 = CenterPt2[i].v3;
            num[i] = 0;
            sum1[i] = sum2[i] = sum3[i] = 0;
        }
        error = 0;
    }//分类结束
    for (int x = 0; x < cols; x++) {//上色
        for (int y = 0; y < rows; y++) {
            Color(&Points[x + y * cols], Points[x + y * cols].id);
        }
    }
    for (int x = 0; x < cols; x++) {//显示
        for (int y = 0; y < rows; y++) {
            K.at<Vec3b>(y, x)[0] = (int)Points[x + y * cols].v1;
            K.at<Vec3b>(y, x)[1] = (int)Points[x + y * cols].v2;
            K.at<Vec3b>(y, x)[2] = (int)Points[x + y * cols].v3;
        }
    }
    //显示图片
    imshow("Output", K);
    //不加此语句图片会一闪而过
    waitKey(0);
    delete[] Points, CenterPt1, CenterPt2, num, sum1, sum2, sum3;
    return K;
}

int main()
{
    //读取图片(使用图片的相对路径)
    Mat src = imread("ik_beijing_c.bmp");
    Kmean(src, 7, 3);
    return 0;
}

/*
int b=img.at<Vec3b>(y,x)[0]; // blue
int g=img.at<Vec3b>(y,x)[1]; // green
int r=img.at<Vec3b>(y,x)[2]; // red
*/

                                                                               分类效果图

由于迭代次数有限,可以看到并不收敛,另外初始点的选择也不是很好,需要改进。

2018-03-16 13:08:24 primetong 阅读数 1520
  • OpenCV4 图像处理与视频分析实战教程

    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API 与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到视频分析,涵盖了计算机视觉与OpenCV4 中主要模块的相关知识点,穿插大量工程编程技巧与知识点与案例,全部课程的PPT课件与源码均可以下载。部分课程内容运行截图:

    1686 人正在学习 去看看 贾志刚

OpenCV图像处理入门学习教程系列,上一篇第二篇:不同阈值二值化图像

图像拼接与融合介绍

图像拼接在实际中的应用场景非常广泛,比如无人机的航拍,遥感图像等,甚至小到我们出去游玩用手机拍照时,无奈广角太小,没有办法一次将所有你要拍的景物全部拍下来(当然现在很多手机都自带全景拍照啦,但是不自己试试怎么拼接多不爽~)

那么现在假如你的手机是老爷机,没有广角镜头,没有全景功能,所以你对某处中国好山水从左往右依次拍了好几张照片,现在的你坐在电脑前,把手机插入电脑,总不能看着这些照片发呆吧!那么我们能不能把这些照片拼接成一个全景图像呢?现在利用OpenCV就可以做到图像拼接生成全景图像的效果!


比如我们有以下这样的两张图要进行拼接,还得考虑平时拍照时手抖拍斜了的情况嘛!


大家看到可能会大喊:哎呀!这是哪个手残党拍的照片!拍斜了不说,光线还差了那么多,这拼起来多丑对不对!不要怕,只要满足图像拼接的基本需求——两张图有比较多的重叠部分,其他一切都不是问题~


图像拼接技术主要包括两个关键环节即图像配准和图像融合。对于图像融合部分,由于其耗时不太大,且现有的几种主要方法效果差别也不多,所以总体来说算法上比较成熟。

而图像配准部分是整个图像拼接技术的核心部分,它直接关系到图像拼接算法的成功率和运行速度,因此配准算法的研究是多年来研究的重点。

现在CV领域有很多特征点的定义,比如sift、surf、harris角点、ORB都是很有名的特征因子,都可以用来做图像拼接的工作,他们各有优势。本文将使用基于SIFT和SURF特征进行微旋转图像的图像拼接,用其他方法进行拼接也是类似的,简单来说都是由以下几步完成,区别在于特征点的提取不同:

(1)特征点提取和描述。
(2)特征点配对,找到两幅图像中匹配点的位置。
(3)通过配对点,生成变换矩阵,并对图像1应用变换矩阵生成对图像2的映射图像,即图像配准。
(4)图像2拼接拷贝到映射图像上,完成拼接。
通过以上4步之后,完成了基础的拼接过程。如果还想对重叠边界进行特殊处理,可以考虑图像融合(去裂缝处理)。



一、OpenCV2和3不同的环境配置

  • IDE:Visual Studio 2013

  • 语言:C++

  • 依赖OpenCV 2.4.9、3.3.0


二、基于SIFT特征的微旋转图像拼接与融合生成全景图像

下面援引一些网上对于SIFT的描述方便大家理解。SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。该方法于1999年由David Lowe首先发表于计算机视觉国际会议(International Conference on Computer Vision,ICCV),2004年再次经David Lowe整理完善后发表于International journal of computer vision(IJCV)。
SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用SIFT特征描述对于部分物体遮蔽的侦测率也相当高,只需要少量的SIFT物体特征就足以计算出位置与方位。

SIFT特征检测主要包括以下4个基本步骤:
1. 尺度空间极值检测:
搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。
2. 关键点定位
在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
3. 方向确定
基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
4. 关键点描述
在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。

SIFT特征匹配主要包括2个阶段:
第一阶段:SIFT特征的生成,即从多幅图像中提取对尺度缩放、旋转、亮度变化无关的特征向量。
第二阶段:SIFT特征向量的匹配。
SIFT特征的生成一般包括以下几个步骤:
1. 构建尺度空间,检测极值点,获得尺度不变性。


2. 特征点过滤并进行精确定位。


3. 为特征点分配方向值。


4. 生成特征描述子。
以特征点为中心取16×16的邻域作为采样窗口,将采样点与特征点的相对方向通过高斯加权后归入包含8个bin的方向直方图,最后获得4×4×8的128维特征描述子。示意图如下:


当两幅图像的SIFT特征向量生成以后,下一步就可以采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图1的某个关键点,通过遍历找到图像2中的距离最近的两个关键点。在这两个关键点中,如果最近距离除以次近距离小于某个阈值,则判定为一对匹配点。
SIFT特征匹配的例子:



基于SIFT特征的微旋转图像拼接生成全景图像效果如下(还未融合,原图见上文章开头的两图):


融合后:

再来点测试结果,方便与之后的SURF特征作比较:

左图:

右图:

可以看到待拼接的左图、右图是有明显的、角度不大的旋转。现在给出基于SIFT特征的微旋转图像拼接与融合生成全景图像运行结果:

以及效果:


三、基于SURF特征的微旋转图像拼接与融合生成全景图像

用SIFT算法来实现图像拼接是很常用的方法,但是因为SIFT计算量很大,所以在速度要求很高的场合下不再适用。所以,它的改进方法SURF因为在速度方面有了明显的提高(速度是SIFT的3倍),所以在图像拼接领域还是大有作为(虽然说SURF精确度和稳定性不及SIFT,这点接下来就通过实际效果图进行比较)。下面将给出基于SIFT特征的微旋转图像拼接与融合生成全景图像的运行结果和效果图。

与SIFT比较的第一张图,拼接融合后的:

第二张的运行结果:

以及其效果图:

四、代码解析

刚刚使用到的基于SIFT特征和SURF特征的微旋转图像拼接与融合生成全景图像的代码如下:
  • IDE:Visual Studio 2013

  • 语言:C++

  • 依赖OpenCV 3.3.0

其实这两种特征算子的代码是一模一样的,只需要在提取特征点的时候稍微修改函数参数即可:
	//提取特征点(源代码第27行)    
	Ptr<Feature2D> f2d = xfeatures2d::SIFT::create();	//修改代码中的SIFT参数即可修改算法(比如SURF等)
部分参考代码如下,相应位置有详细注释,整个工程文件见下载页面
#include <opencv2/opencv.hpp>  
#include "highgui/highgui.hpp"    
#include <opencv2/xfeatures2d.hpp>

using namespace cv;
using namespace std;

//计算原始图像点位在经过矩阵变换后在目标图像上对应位置  
Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri);

int main(int argc, char *argv[])
{
	Mat image01 = imread("left2.jpg");
	Mat image02 = imread("right2.jpg");

	if (image01.data == NULL || image02.data == NULL)
		return 0;
	imshow("待拼接图像左图", image01);
	imshow("待拼接图像右图", image02);

	//灰度图转换  
	Mat image1, image2;
	cvtColor(image01, image1, CV_RGB2GRAY);
	cvtColor(image02, image2, CV_RGB2GRAY);

	//提取特征点    
	Ptr<Feature2D> f2d = xfeatures2d::SIFT::create();	//修改SIFT参数即可修改算法(比如SURF)
	vector<KeyPoint> keyPoint1, keyPoint2;
	f2d->detect(image1, keyPoint1);
	f2d->detect(image2, keyPoint2);

	//特征点描述,为下边的特征点匹配做准备    
	Mat imageDesc1, imageDesc2;
	f2d->compute(image1, keyPoint1, imageDesc1);
	f2d->compute(image2, keyPoint2, imageDesc2);

	//获得匹配特征点,并提取最优配对     
	FlannBasedMatcher matcher;
	vector<DMatch> matchePoints;
	matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());
	sort(matchePoints.begin(), matchePoints.end()); //特征点排序    
	//获取排在前N个的最优匹配特征点  
	vector<Point2f> imagePoints1, imagePoints2;
	for (int i = 0; i<10; i++)
	{
		imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
		imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
	}

	//获取图像1到图像2的投影映射矩阵,尺寸为3*3  
	Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);
	Mat adjustMat = (Mat_<double>(3, 3) << 1.0, 0, image01.cols, 0, 1.0, 0, 0, 0, 1.0);
	Mat adjustHomo = adjustMat*homo;

	//获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位  
	Point2f originalLinkPoint, targetLinkPoint, basedImagePoint;
	originalLinkPoint = keyPoint1[matchePoints[0].queryIdx].pt;
	targetLinkPoint = getTransformPoint(originalLinkPoint, adjustHomo);
	basedImagePoint = keyPoint2[matchePoints[0].trainIdx].pt;

	//图像配准  
	Mat imageTransform1;
	warpPerspective(image01, imageTransform1, adjustMat*homo, Size(image02.cols + image01.cols + 110, image02.rows));

	//在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变  
	Mat image1Overlap, image2Overlap; //图1和图2的重叠部分     
	image1Overlap = imageTransform1(Rect(Point(targetLinkPoint.x - basedImagePoint.x, 0), Point(targetLinkPoint.x, image02.rows)));
	image2Overlap = image02(Rect(0, 0, image1Overlap.cols, image1Overlap.rows));
	Mat image1ROICopy = image1Overlap.clone();  //复制一份图1的重叠部分  
	for (int i = 0; i<image1Overlap.rows; i++)
	{
		for (int j = 0; j<image1Overlap.cols; j++)
		{
			double weight;
			weight = (double)j / image1Overlap.cols;  //随距离改变而改变的叠加系数  
			image1Overlap.at<Vec3b>(i, j)[0] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[0] + weight*image2Overlap.at<Vec3b>(i, j)[0];
			image1Overlap.at<Vec3b>(i, j)[1] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[1] + weight*image2Overlap.at<Vec3b>(i, j)[1];
			image1Overlap.at<Vec3b>(i, j)[2] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[2] + weight*image2Overlap.at<Vec3b>(i, j)[2];
		}
	}
	Mat ROIMat = image02(Rect(Point(image1Overlap.cols, 0), Point(image02.cols, image02.rows)));  //图2中不重合的部分  
	ROIMat.copyTo(Mat(imageTransform1, Rect(targetLinkPoint.x, 0, ROIMat.cols, image02.rows))); //不重合的部分直接衔接上去  
	namedWindow("拼接结果-SIFT", 0);
	imshow("拼接结果-SIFT", imageTransform1);
	imwrite("拼接结果-SIFT.jpg", imageTransform1);
	waitKey();
	return 0;
}

//计算原始图像点位在经过矩阵变换后在目标图像上对应位置  
Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri)
{
	Mat originelP, targetP;
	originelP = (Mat_<double>(3, 1) << originalPoint.x, originalPoint.y, 1.0);
	targetP = transformMaxtri*originelP;
	float x = targetP.at<double>(0, 0) / targetP.at<double>(2, 0);
	float y = targetP.at<double>(1, 0) / targetP.at<double>(2, 0);
	return Point2f(x, y);
}

五、两种特征的结果比较与总结

1.测试图片不宜过大。

测试用的图片不能过大,如果直接输入拍好的照片(比如3456*4608),不但导致程序需要运行数分钟才出结果,还容易导致拼接失败(主要是特征点匹配太多)。

2.尽量使用静态图片(没有动态因素干扰的)

在第一张测试图片中,我们可以很清楚地看到中间拼接处有一辆黑色车辆的“鬼影”,这是为什么?因为两幅图中的黑色车辆移动刚好在中间接缝处了啊!所以要做图像拼接,尽量保证使用的是静态图片,不要加入一些动态因素干扰拼接。

3.图像融合(去裂缝处理):

在拼接图的交界处,两图因为光照色泽的原因使得两图交界处的过渡很糟糕,所以需要特定的处理解决这种不自然。本文的处理思路是加权融合,在重叠部分由前一幅图像慢慢过渡到第二幅图像,即将图像的重叠区域的像素值按一定的权值相加合成新的图像。

如果没有做去裂缝处理,(SIFT)效果如下:


与有做图像融合处理的效果(见二、的最后一张效果图)相比,拼接处明显很突兀,不自然,有断裂的感觉。

4.基于SIFT特征和SURF特征的微旋转图像拼接与融合生成全景图像的比较。

通过刚刚的处理效果图的比较,可以明显地比较出SIFT的优势在于对待拼接图片小幅度旋转的适应性,精准度较高;而SURF算法对于待拼接图片的平直性要求很高,稍微旋转的图片拼接后已经失真。查阅资料得知SURF算法的优势在于速度方面有明显的提高(速度是SIFT的3倍)。

基于SIFT特征和SURF特征的微旋转图像拼接与融合生成全景图像,整个工程文件见下载页面

OpenCV图像处理入门学习教程系列,下一篇第四篇:基于LoG算子的图像边缘检测

2019-04-30 13:02:48 zsc201825 阅读数 114
  • OpenCV4 图像处理与视频分析实战教程

    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API 与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到视频分析,涵盖了计算机视觉与OpenCV4 中主要模块的相关知识点,穿插大量工程编程技巧与知识点与案例,全部课程的PPT课件与源码均可以下载。部分课程内容运行截图:

    1686 人正在学习 去看看 贾志刚
没有更多推荐了,返回首页