opencv的core组件——像素,ROI,图像混合(3)

ROI区域图像叠加&图像混合
  1. ROI区域图像叠加
    ROI(region of interest):感兴趣区域:利用感兴趣区域ROI实现图像叠加。
    1.1 使用cv::Retc
    定义一个矩形区域。指定矩形左上角坐标和矩阵的长宽就可以定义一个矩形区域
//定义一个Mat 类型并给其设定ROI区域
Mat imageROI;
//方法一
imageROI = image(Rect(500, 250, log.cols, log.rows));

1.2 使用cv::Range
另一种定义ROI的方式是指定感兴趣区域的行或列的范围。Range是指从起始索引到终止索引(不包括)的一连串序列。

Mat imageROI;
imageROI = image(Range(250, 250 + log.rows), Range(200, 200 + log.cols));

1.3 实例:如何将一幅图像加到另一幅图的指定位置


函数名:ROI_AddImage()
描述:利用感兴趣区域实现图像的叠加

bool ROI_AddImge()
{

    //[1]载入图像
    Mat srcImage1 = imread("../1.jpg");
    Mat logImage2 = imread("../2.jpg");
    if(!srcImage1.data) {
        printf("读取srcImage1 false!");
        return false;
    }

    if(!logImage2.data) {
        printf("读取logImage2 false!");
        return false;
    }

    //[2]定义一个Mat类型,并设定ROI区域
    Mat imageROI = srcImage1(Rect(0, 0, logImage2.cols, logImage2.rows));

    //[3]加载掩模(必需是灰度图)
    Mat mask = imread("../2.jpg", 0);

    //[4]将掩模复制到ROI
    logImage2.copyTo(imageROI, mask);

    //[5]显示结果
    namedWindow("<1>利用ROI实现图像的叠加显示窗口");
    imshow("<1>利用ROI实现图像叠加显示窗口", srcImage1);
    return true;
}

这个函数首先载入了两张图片到srcImage和logImage中,然后定义也Mat类型的imageROI,并在srcImage中设定一块矩形的感兴趣区域,将imageROI和srcImage关联起来。然后定义了一个Mat类的mask,读入2.jpg,然后把mask中的内容复制到imageROI中,据得到最终的效果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:一定要保证nameWindow()中的窗口字符串要和imshow()中的一致,否则,会多处一个窗口

  1. 图像混合

2.1 线性混合操作
线性混合操作是一种典型的二元(两个图像)的像素操作,它的理论公式为:
g(x) = (1-a) f 2 f_2 f2(x) + a f 1 f_1 f1(x)
我们通过在0~1之间改变alpha的值,来实现两张图片或两段视频产生的时间上的画面的叠加效果,图片间的过渡叠加效果,视频的画面叠加效果——addWeighted

2.2 addWeighted函数
(1), 计算两个数组(图像阵列)的加权和。因为要对两个图像进行加权求和,所以图像尺寸一定要相同。
原型:
void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype = -1);
参数:

  • 参数一:InputArray src1, 表示加权的第一个数组, InputArray相当于Mat.
  • 参数二:alpha,第一个数组的权重。
  • 参数三:src2,第二个数组,它需要和第一个数组有相同的尺寸和通道数字(颜色)。
  • 参数四:beta,表示第二个数组的权重值。
  • 参数五:dst, 输出的数组,和输入的两个数组有相同的尺寸和通道数。
  • 参数六:gamma,一个加权到权重总和上的标量值。
  • 参数七:dtype, 输出阵列的可选深度,默认-1.当两个输入数组具有相同的深度时,等同于src1.depth()

dst = src1[I] * alpha + src2[I] * beta + gamma;
其中I是多维数组元素的索引值。遇到多通道时,每个通道都需要单独处理。当输出数组的深度为CV_32S时,这个函数就不适用了,内存会益处或值不对。

2.3 代码实例


函数名:LinearBlending()
描述:图像混合加权操作,叠加效果图

bool LinearBlending()
{
    //[0]定义一些局部变量
    double alphaValue = 0.5;
    double betaValue;

    Mat srcImage2, srcImage3, dstImage;

    //[1]读取图片(两幅图同尺度和类型)
    Mat srcImage2 = imread("../3.jpg");
    Mat srcImage3 = imread("../4.jpg");
    if(!srcImage2.data) {
        cout <<"读取srcImage2 错误!" << endl;
        return false;
    }
    if(!srcImage3.data) {
        cout << "读取srcImage3 错误!" << endl;
        return false;
    }

    //[3]作图像混合加权操作
    betaValue = (1.0 - alphaValue);
    addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);

    //[3]创建并显示原窗口
    namedWindow("<2>线性混合窗口原图>", 1);
    imshow("<2>线性混合窗口原图【窗口】", srcImage2);

    namedWindow("<2>线性混合窗口【效果图】");
    imshow("<2>线性混合窗口【效果图】", dstImage);

    return true;

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.初级图像混合
3.1 将感兴趣区域和addWeightd进行结合。
先指定ROI区域,然后用addWeighted函数对指定的ROI区域的图像进行混合操作。


函数名:ROI_LinearBlending()
描述:线性混合实现函数,指定区域线性图像混合;

bool ROI_LiearBlending()
{
    //[1]读取图像
    Mat srcImage4 = imread("../1.jpg", 1);
    Mat logsrcImage = imread("../2.jpg");

    if(!srcImage4.data) {
        cout <<"读取srcImage2 错误!" << endl;
        return false;
    }
    if(!logsrcImage.data) {
        cout << "读取srcImage3 错误!" << endl;
        return false;
    }

    //[2]定义一个Mat类的ROI,并设定区域
    Mat imageROI;
    //方法一
    imageROI = srcImage4(Rect(0, 0 , logsrcImage.cols, logsrcImage.rows));

    //方法二
    imageROI = srcImage4(Range(250, 250 + logsrcImage.rows), Range(200, 200 + logsrcImage.cols));

    //[3]将logo加到原图上
    addWeighted(imageROI, 0.5, logsrcImage, 0.3, 0.0, imageROI);

    //[4]显示结果
    namedWindow("<4>区域线性混合图实例");
    imshow("<4>区域线性混合实例图", srcImage4);

    return 0;

}

在这里插入图片描述

4 综合实例

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;

bool ROI_AddImage();
bool LinearBlending();
bool ROI_LiearBlending();

int main()
{
    system("color SE");
    if(ROI_AddImage() && LinearBlending() && ROI_LiearBlending()) {
        cout << "运行成功!" << endl;
    }

    waitKey(0);
    return 0;
}


bool ROI_AddImge()
{

    //[1]载入图像
    Mat srcImage1 = imread("../1.jpg");
    Mat logImage2 = imread("../2.jpg");
    if(!srcImage1.data) {
        printf("读取srcImage1 false!");
        return false;
    }

    if(!logImage2.data) {
        printf("读取logImage2 false!");
        return false;
    }

    //[2]定义一个Mat类型,并设定ROI区域
    Mat imageROI = srcImage1(Rect(0, 0, logImage2.cols, logImage2.rows));

    //[3]加载掩模(必需是灰度图)
    Mat mask = imread("../2.jpg", 0);

    //[4]将掩模复制到ROI
    logImage2.copyTo(imageROI, mask);

    //[5]显示结果
    namedWindow("<1>利用ROI实现图像的叠加显示窗口");
    imshow("<1> 利用ROI实现图像叠加显示窗口", srcImage1);
    return true;
}


bool LinearBlending()
{
    //[0]定义一些局部变量
    double alphaValue = 0.5;
    double betaValue;

    Mat srcImage2, srcImage3, dstImage;

    //[1]读取图片(两幅图同尺度和类型)
    Mat srcImage2 = imread("../3.jpg");
    Mat srcImage3 = imread("../4.jpg");
    if(!srcImage2.data) {
        cout <<"读取srcImage2 错误!" << endl;
        return false;
    }
    if(!srcImage3.data) {
        cout << "读取srcImage3 错误!" << endl;
        return false;
    }

    //[3]作图像混合加权操作
    betaValue = (1.0 - alphaValue);
    addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);

    //[3]创建并显示原窗口
    namedWindow("<2>线性混合窗口原图>", 1);
    imshow("<2>线性混合窗口原图【窗口】", srcImage2);

    namedWindow("<2>线性混合窗口【效果图】");
    imshow("<2>线性混合窗口【效果图】", dstImage);

    return true;

}

bool ROI_LiearBlending()
{
    //[1]读取图像
    Mat srcImage4 = imread("../1.jpg", 1);
    Mat logsrcImage = imread("../2.jpg");

    if(!srcImage4.data) {
        cout <<"读取srcImage2 错误!" << endl;
        return false;
    }
    if(!logsrcImage.data) {
        cout << "读取srcImage3 错误!" << endl;
        return false;
    }

    //[2]定义一个Mat类的ROI,并设定区域
    Mat imageROI;
    //方法一
    imageROI = srcImage4(Rect(0, 0 , logsrcImage.cols, logsrcImage.rows));

    //方法二
    imageROI = srcImage4(Range(250, 250 + logsrcImage.rows), Range(200, 200 + logsrcImage.cols));

    //[3]将logo加到原图上
    addWeighted(imageROI, 0.5, logsrcImage, 0.3, 0.0, imageROI);

    //[4]显示结果
    namedWindow("<4>区域线性混合图实例");
    imshow("<4>区域线性混合实例图", srcImage4);

    return 0;

}

CMakeLists.txt:


cmake_minimum_required(VERSION 3.6)
project(ROI)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(ROI ROI.cpp)
target_link_libraries(ROI  ${OpenCV_LIBS})

实现效果是上述分装好的三个函数运行后的结果。

  1. 访问图像中的像素

5.1 图像在内存中的存储方式
图像矩阵的大小取决于所用的颜色模型,即所用的通道数。
如果是灰度图,则列数就是通道数,一一对应。
如果是彩色图,那就是多通道的,矩阵中的列会包含多个子列,子列个数与通道数相同。
opencv中列的通道数是反过来的,不是RGB而是BGR,很多情况下,由于内存空间足够大,可以实现连续存储,因此,图像个行能连续存储,形成一个长行。连续存储能提升图像扫描的速度,可以通过使用isContinuous()来判断矩阵是否连续存储的。

5.2 颜色空间缩减
矩阵的单通道像素值由无符号字符类表示,就是256个不同的值,多通道的更多,如此多的颜色处理起来,会影响算法的性能。
使用这些颜色中有代表性的就可以达到同样的效果。
颜色空间缩减:将现有的颜色值除以某个值,以获得较少的颜色数。如0-9可以取为0, 10-19可以取为10;
这样的缩减操作将三通道的颜色降低到26 x 26 x26种。
在C++中int类型除法会自动截余。
I n e w I_{new} Inew= I o l d 10 ∗ 10 \frac {I_{old}}{10}*10 10Iold10
在处理图像的过程中,对每个像素进行上述操作,也需要花时间。只有0~255中情况。进一步把256种计算好的结果存到table中,这样每种情况不用计算,直接取值。

		int divideWith = 10;
		uchar table[256];
		for(int i = 0; i < 256; i++) {
			table[i] = divideWith * (i/divideWith);
		}

于是,table中存放的是值为i的像素减小颜色空间的结果。
颜色空间缩减有两部分组成:
(1)遍历图像矩阵的每个像素;
(2)对像素应用上述公式;

5.3 LUT函数:Look up table操作
通过operationsOnArrays::LUT()的函数来进行。它用于批量进行图像元素的查找,扫描于操作图像。使用方法如下:

		首先建立一个Mat 型用于查表
		Mat loolUpTable(1, 256, CV_8U);
		uchar *p = LookUpTable.data;
		for(int i  = 0; i < 256; i++)
		{
			p[i] = table[i];
		}
		//然后调用函数(I是输入,J是输出)
		for(int i = 0; i<times; i++) {
			LUT(I, lookUpTable, J);

5.4 记时函数
getTickCount() , getTickFrequency()

  • getTickCount()返回CPU自某个事件以来时钟周期数;
  • getTickFrequency()函数返回CPU一秒走的时钟周期数。

使用方法:

	double time0 = static_cast<double>(getTickCount)); 
	//进行一些列图像操作
	time 1 = ((double)getTickCount() - time0)/getFrequency();
	cout << "操作运行的时间为:" << time0 << "秒“   << endl;

5.5 访问图像中像素的三种方法
OpenCV提供了三种访问每个像素的方法;

  • 方法一:指针访问:c擦作符[];
  • 方法二:迭代器iterator;
  • 方法三:动态地址计算;
    这三种访问的方式在bebug模式下访问速度略有差别,release下差不多。
    程序的目的是减少图像中颜色的数量。
    主程序:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void colorReduce(Mat& inputImage, Mat& outputImage, int div);

int main()
{
    //[1]创建原始图像并显示
    Mat srcImage = imread("../1.jpg");
    imshow("原始图像", srcImage);

    //[2]按原始图像的参数规格来创建效果图
    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());

    //[3]记录时间
    double time0 = static_cast<double>(getTickCount());

    //[4]调用颜色空间缩减函数
    colorReduce(srcImage, dstImage, 32);

    //[5]计算运行时间并输出
    time0 = ((double)getTickCount() - time0)/getTickFrequency();
    cout << "此方法运行时间:" << time0 << "秒" << endl;

    //[6]显示效果图
    imshow("效果图", dstImage);
    waitKey(0);

    return 0;

}

从上述代码可以看出,主程序中调用了colorReduce()函数来完成减少颜色的的工作,而我们访问像素有三种方法,所以有三个版本的colorReduce()

  • 方法一:用指针访问像素
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
	outputImage = inputImage.clone();  // 复制实参到临时变量
	int rowNumber = outputImage.rows;   
	int colorNumber = outputImage.cols * outputImage.channels();  //列x的通道数等于每一行元素的个数
  for(int i = 0; i < rowNumber; i++) {
	uchar* data = outputImage.ptr<uchar>(i);  //获取第i行的首地址
	for(int j = 0; j<colNumber; j++) {
		//开始处理每个像素
		data[j] = data[j]/div * div +div/2;
		//*data ++= *data/div * div + div/2; //使用指针运算从一列移动到下一列
		//处理结束
	}
  }
	
}

在这里插入图片描述
在这里插入图片描述

  • 方法二:迭代器操作像素
    在迭代法中,我们需要做的就是获取图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符加到指针的前面,可以访问当前指针所指的内容。
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();

    //获取迭代器
    Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();  //初始位置的迭代器
    Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>(); //终止

    //存取彩色图像 像素
    for(; it!= itend; ++it) {
        (*it)[0] = (*it)[0]/div * div + div/2;
        (*it)[1] = (*it)[1]/div * div + div/2;
        (*it)[2] = (*it)[2]/div * div + div/2;
        
    }
}

在这里插入图片描述

  • 方法三:动态地址计算
    用动态地址计算来操作像素,配合at方法。
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    int rowNumber = outputImage.rows;   
	int colNumber = outputImage.cols;

    //存取彩色图像像素
    for(int i = 0; i < rowNumber; i++) {
        for(int j = 0; j < colNumber; j++) {
            //开始处理每个像素
            outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0] /div * div + div/2; // 蓝色通道
            outputImage.at<Vec3b>(i,j)[1] = outputImage.at<Vec3b>(i, j)[1] / div * div + div/2; //绿色
            outputImage.at<Vec3b>(i,j)[2] = outputImage.at<Vec3b>(i, j)[2] / div * div + div/2; //红色
            //处理结束
        }
    }
}

在这里插入图片描述
讲解上述代码:
成员函数at(int y, int x)可以用来存取图像元素,但必须在编译时知道图像的数据类型。我们一定要确保指定的数据类型和矩阵中的数据类型符合。因为at方法本身不会对任何数据类型进行转换。

对彩色图像,像素由三个部分组成:蓝通道,绿通道,红通道(BGR)。因此对于一个包含彩色图像的Mat,会返回一个由三个8位数组成的向量。 类型为Vec3b,即由三个unsigned char 组成的向量。
形式:image.at(j, i)[channel] = value; channel表明了颜色通道号;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Opencv中的分水岭算法(watershed algorithm)是一种自动图像分割算法,常用于分离图像中的前景和背景。下面是使用Opencv中的分水岭算法进行图像分割的步骤: 1. 读入图像并进行预处理,如去噪、灰度化等。 2. 对图像进行二值化处理,得到前景和背景的二值图像。 3. 对二值图像进行距离变换(distance transform),得到每个像素离最近的背景像素的距离。 4. 对距离变换后的图像进行阈值处理,得到图像的分水岭标记(watershed markers)。 5. 对分水岭标记进行修正,确保标记不会重叠或出现空洞。 6. 对分水岭标记应用分水岭算法,得到分割后的图像。 下面是使用Opencv实现分水岭算法的示例代码: ```python import cv2 import numpy as np # 读取图像并进行预处理 img = cv2.imread('input.jpg') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) # 进行距离变换 dist_transform = cv2.distanceTransform(thresh,cv2.DIST_L2,5) ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0) # 获取分水岭标记 sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(thresh,sure_fg) ret, markers = cv2.connectedComponents(sure_fg) markers = markers+1 markers[unknown==255] = 0 # 修正分水岭标记 markers = cv2.watershed(img,markers) img[markers == -1] = [255,0,0] # 显示分割结果 cv2.imshow('result', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 这段代码中,我们首先读入一张图像并进行预处理,然后进行距离变换,得到每个像素到最近的背景像素的距离。接着使用阈值处理得到分水岭标记,修正分水岭标记并应用分水岭算法,最后显示分割结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值