【OpenCV C++&Python】(二)图像基本操作

简介:

图像基本操作

由于Python比较简单,所以后续的文章都是先Python后C++的顺序。

Python

说明:OpenCV-Python使用了Numpy。所有OpenCV数组结构都与Numpy数组相互转换。这也使得它更容易与其他使用Numpy的库集成,比如SciPy和Matplotlib。

获取和修改像素值

加载彩色图像:

import cv2 as cv
image = cv.imread('image0.jpg')
cv.imshow("Display window", image )
k = cv.waitKey(0)

获取图像像素值:

px = image [100, 100]
print('(100,100)的像素值:', px)

blue = image [100, 100, 0]
print('(100,100)蓝色通道的像素值:', blue)
(100,100)的像素值: [187 187 187]
(100,100)蓝色通道的像素值: 187

修改像素值:

image [100, 100] = [255, 255, 255]
print(image [100, 100])
[255 255 255]

上述方法通常用于选择数组的区域,例如前5行和后3列。对于单个像素访问,Numpy数组方法:array.item()array.itemset()更好。

print(image .item(10, 10, 2))
232

image .itemset((10, 10, 2), 100)
print(image .item(10, 10, 2))
100

获取图像属性

图像的形状:

print('图片大小:', np.shape(image ))
图片大小: (681, 690, 3)

(行数、列数,通道数)

说明: 如果图像是灰度图像,则返回的元组仅包含行数和列数,因此,这是检查加载的图像是灰度图像还是彩色图像是一种很好的方法。

像素总数:

print(image .size)
1409670

图像数据类型:

print(image .dtype)
uint8

彩色转换为灰度

从彩色转换为灰度:

grey = cv.cvtColor(image , cv.COLOR_BGR2GRAY)

图像的感兴趣区域(ROI)

有时,你只需要处理图像的某些区域。例如,对人脸图像中的眼睛进行检测。当获得一张人脸时,我们可以只选择人脸区域并搜索其中的眼睛,而不是搜索整个图像。它提高了准确性和性能。

使用Numpy索引获得ROI:这里,我选择猴头并将其复制到图像中的另一个区域:

head = image [300:390, 150:250]
image [350:440, 60:160] = head
cv.imshow("Display window", image )
k = cv.waitKey(0)

分离和合并图像通道

有时你需要单独处理图像的B、G、R通道。在这种情况下,你可以使用cv.split

b, g, r = cv.split(image )

b = image [:,:,0]

在其他情况下,你可能需要用单独的通道来创建BGR图像:

image = cv.merge((b,g,r))

假设要将所有红色像素设置为零,则无需首先分割通道。Numpy索引速度更快:

image [:,:,2] = 0

cv.split()是一个代价高昂的操作(就时间而言)。最好使用Numpy索引代替。

为图像制作边框(填充)

如果想在图像周围创建边框,比如相框,可以使用 cv.copyMakeBorder()。它在卷积运算、零填充等方面有更多应用。该函数采用以下参数:

  • src:输入图像
  • top, bottom, left, right:相应方向上边框所占像素数。
  • borderType:定义要添加的边框类型的标志。它可以是以下类型
cv.BORDER_CONSTANT : 添加特定颜色的边框。该值应作为下一个参数给出。


cv.BORDER_REFLECT :边界将是图像边界像素的镜像,如下所示:fedcba | abcdefgh | hgfecb


cv.BORDER_REFLECT_101 or cv.BORDER_DEFAULT:同上,但有一点变化,如下所示:gfedcb | abcdefgh | gfedcba


cv.BORDER_REPLICATE:最后一个元素被复制,如下所示:aaaaa | abcdefgh | hhhhh

cv.BORDER_WRAP:难以用文字解释,它看起来是这样的:cdefgh | abcdefgh | abcdefg
  • value:如果边框类型为cv.BORDER_CONSTANT,则为边框的颜色。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

BLUE = [255, 0, 0] 


image = cv.imread('image1.jpg')

replicate = cv.copyMakeBorder(image , 30, 30, 30, 30, cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(image , 30, 30, 30, 30, cv.BORDER_REFLECT)
reflect301 = cv.copyMakeBorder(image , 30, 30, 30, 30, cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(image , 30, 30, 30, 30, cv.BORDER_WRAP)
constant = cv.copyMakeBorder(image , 30, 30, 30, 30, cv.BORDER_CONSTANT, value=BLUE)
plt.subplot(231), plt.imshow(image , 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect301, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()

opencv是以BGR模式加载图像,而matplotlib是以RGB模式显示图像

图像用matplotlib显示。因此红色和蓝色通道将互换:

将图像转换为RGB,再用matplotlib显示:

plt.subplot(231), plt.imshow(image[..., ::-1], 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate[..., ::-1], 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect[..., ::-1], 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect301[..., ::-1], 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap[..., ::-1], 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant[..., ::-1], 'gray'), plt.title('CONSTANT')

图像相加

可以使用OpenCV函数cv.add(),将两个图像加起来。或者简单地通过numpy操作res=img1+img2。两个图像的深度和类型应该相同,或者第二个图像用标量值替代。

说明: OpenCV加法和Numpy加法之间存在差异。OpenCV加法是一种饱和(saturated) 运算,而Numpy加法是一种模(modulo) 运算。

例如:

import cv2 as cv
import numpy as np

x = np.uint8([250])
y = np.uint8([10])
print(cv.add(x, y))  # 250+10 = 260 => 255
print(x + y)  # 250+10 = 260 % 256 = 4

输出:

[[255]]
[4]

当你将两张图片相加时,最好使用OpenCV的函数,因为它们将提供更好的结果。

图像混合

这也是图像相加,但为图像赋予不同的权重,以提供混合或透明的感觉。按照以下等式混合图像:

$$ g(x)=(1- \alpha ) f_{0} (x)+ \alpha f_{1} (x) $$

通过将$α$从0调到1、可以在一个图像到另一个图像之间执行渐变。

下面,我们将两张图像混合在一起。第一张图像的权重为0.3,第二张图像的权重为0.7。 cv.addWeighted()将以下等式应用于两张图像:

$$ dst=\alpha\cdot img1+\beta\cdot img2+\gamma $$

这里令$\alpha=0.3,\beta=0.7,\gamma=0$。

img1 = cv.imread('image0.jpg')
img2 = cv.imread('image1.jpg')
dst = cv.addWeighted(img1, 0.3, img2, 0.7, 0)
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()

位运算

这包括按位AND、OR、NOT和XOR操作。它们在定义和使用非矩形ROI、提取图像的任意部分等时,都非常有用。下面是一个如何更改图像特定区域的示例。

我们想把img2的标志放在一张图片img1上面。如果将两个图像相加,它会改变颜色。如果我将它们混合,我会得到一个透明的效果。这都是我们不希望的。如果是矩形区域,我可以像上文一样使用矩形的ROI,但是img2的标志不是矩形的。因此,我们可以使用如下所示的按位操作:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

img1 = cv.imread('image.jpg')
img2 = cv.imread('mask.jpg')
plt.subplot(241), plt.imshow(img1[..., ::-1]), plt.title('img1')
plt.subplot(242), plt.imshow(img2[..., ::-1]), plt.title('img2')

# 把标志放在左上角,所以先定义ROI
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
# 现在创建一个标志的mask,并创建其反向mask
img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY) # THRESH_BINARY二值化
mask_inv = cv.bitwise_not(mask)  # 标志部分为0,其他为1
# 现在将ROI中的标志区域涂上黑色
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)  # if mask(i)≠0: img1_bg(i)=src1(i) AND src2(I); else  img1_bg(i)=0
# 仅取标志图像中标志区域的部分。
img2_fg = cv.bitwise_and(img2, img2, mask=mask)
# 将标志放入ROI并修改主图像
destination = cv.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = destination
plt.subplot(243), plt.imshow(mask, 'gray'), plt.title('mask')
plt.subplot(244), plt.imshow(mask_inv, 'gray'), plt.title('mask_inv')
plt.subplot(245), plt.imshow(img1_bg[..., ::-1]), plt.title('img1_bg')
plt.subplot(246), plt.imshow(img2_fg[..., ::-1]), plt.title('img2_fg')
plt.subplot(247), plt.imshow(destination[..., ::-1]), plt.title('img1_bg + img2_fg')
plt.subplot(248), plt.imshow(img1[..., ::-1]), plt.title('img1')
plt.show()

使用OpenCV度量程序性能

运行时间是一个程序性能的重要指标。cv.getTickCount 函数返回从一个参考事件(比如机器被打开的那一刻)到调用该函数的那一刻的时钟周期数。因此,如果在函数执行之前和之后调用它,就会得到用于执行函数的时钟周期数。

cv.getTickFrequency函数返回时钟周期的频率,或每秒的时钟周期数。因此,要以秒为单位计算执行时间,可以执行以下操作:

e1 = cv.getTickCount()
# 你的代码
e2 = cv.getTickCount()
time = (e2 - e1) / cv.getTickFrequency()

举个例子,对图像进行多次中值滤波,核的大小为5到49之间的奇数(不考虑实际意义)。:

img1 = cv.imread('image.jpg')
e1 = cv.getTickCount()
for i in range(5, 49, 2):
    img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1) / cv.getTickFrequency()
print("{}秒".format(t))
1.0786秒

你可以用Python的time 模块做同样的事情。而不是cv.getTickCount:利用time.time() 函数。然后取两次的差。

import time
e1 = time.time()
for i in range(5, 49, 2):
    img1 = cv.medianBlur(img1, i)
e2 = time.time()
t = e2 - e1
print("{}秒".format(t))
1.0724830627441406秒

OpenCV中的默认优化

许多OpenCV函数都使用SSE2、AVX等进行了优化。因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们)。你可以使用cv.useOptimized() 检查是否启用/禁用并用cv.setUseOptimized()来启用/禁用它。让我们看一个简单的例子。

image= cv.imread('image.jpg')
print(cv.useOptimized())
t1 = time.time()
res1 = cv.medianBlur(image, 49)
t2 = time.time()
print("{}.秒".format(t2 - t1))

cv.setUseOptimized(False)  # 禁用优化
print(cv.useOptimized())
t1 = time.time()
res2 = cv.medianBlur(image, 49)
t2 = time.time()
print("{}.秒".format(t2 - t1))
True
0.05300545692443848.秒
0.06483578681945801.秒

如你所见,优化后的比未优化的版本更快。

你可以使用它在代码顶部启用或禁用优化。(请记住,它在默认情况下处于启用状态)

cv.setUseOptimized(True) # 启用
cv.setUseOptimized(False) # 禁用

C++

获取和修改像素值

加载彩色图像:

 Mat image = imread("image0.jpg");

为了获得图像像素值,你必须知道图像的类型和通道数。以单通道灰度图像(8UC1型)和像素坐标$(x,y)$为例:

 Scalar intensity = image .at<uchar>(y, x);

intensity.val[0] 包含一个从0到255的值(灰度图只有一个通道:0)。请注意$x$和$y$的顺序(y, x)。在OpenCV中,图像使用矩阵表示,所以我们约定:使用基于0的行索引(或y坐标)和基于0的列索引(或x坐标)。

或者,你还可以使用Point(x, y)获取像素值:

 Scalar intensity = image .at<uchar>(Point(x, y));

现在让我们考虑一个带有BGR(imread的默认返回格式)颜色排序的3通道图像:

Vec3b intensity = image.at<Vec3b>(y, x);
uchar blue = intensity.val[0];   # B通道的像素
uchar green = intensity.val[1];  # G通道的像素
uchar red = intensity.val[2];   # R通道的像素

对于浮点图像,可以使用相同的方法:

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

可以使用相同的方法更改像素值:

image .at<uchar>(y, x) = 128;

获取图像属性

图像的形状:

image .rows   // 行数
image .cols   // 列数
image .channels() // 通道数

像素总数:

image .total()

图像数据类型:

image .type()

如果值为16,则表示:CV_8UC3

图片来源:https://blog.csdn.net/eswai/article/details/52831205

从彩色转换为灰度

Mat grey;
cvtColor(image , grey, COLOR_BGR2GRAY);

图像的感兴趣区域(ROI)

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    Mat image = imread("image0.jpg");
    // ROI
    Mat roiA = image(Range(300, 390), Range(150, 250));
    // 目标ROI
    Mat roiB = image(Range(350, 440), Range(60, 160));
    // 将A赋值到B
    roiA.copyTo(roiB);
    imshow("Display window", image);
    waitKey(0);
    return 0;
}

分离和合并图像通道

#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main() {
    Mat image = imread("image.jpg");     
    vector<Mat> channels;
    //    通道分离
    split(image, channels);
    //   分别得到不同的颜色通道
    Mat blue, green, red;
    blue = channels.at(0);
    green = channels.at(1);
    red = channels.at(2);
    //    通道合并
    Mat dstImage;
    vector<Mat> channels2;
    channels2.push_back(blue);
    channels2.push_back(green);
    channels2.push_back(red);
    merge(channels2, dstImage);
    imshow("图像展示 ", dstImage);
    waitKey(0);
    return 0;
}

为图像制作边框(填充)

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    Mat image = imread("image0.jpg");
    Mat replicate, reflect, reflect301, wrap, constant;
    copyMakeBorder(image, replicate, 30, 30, 30, 30, BORDER_REPLICATE);
    copyMakeBorder(image, reflect, 30, 30, 30, 30, BORDER_REFLECT);
    copyMakeBorder(image, reflect301, 30, 30, 30, 30, BORDER_REFLECT_101);
    copyMakeBorder(image, wrap, 30, 30, 30, 30, BORDER_WRAP);
    copyMakeBorder(image, constant, 30, 30, 30, 30, BORDER_CONSTANT, Scalar(255, 0, 0));
    imshow("replicate", replicate);
    imshow("reflect", reflect);
    imshow("reflect301", reflect301);
    imshow("wrap", wrap);
    imshow("constant", constant);
    waitKey(0);
    return 0;
}

图像相加

#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main() 
{
    Mat M1(2, 2, CV_8UC3, Scalar(180, 150, 2));
    Mat M2(2, 2, CV_8UC3, Scalar(130, 150, 4));
    Mat dst;
    add(M1, M2, dst);
    cout << dst << endl;
}

图像混合

#include <opencv2\opencv.hpp>
using namespace cv;
int main() 
{
    Mat src1 = imread("image0.jpg");
    Mat src2 = imread("image1.jpg");
    Mat dst;
    addWeighted(src1, 0.3, src2, 0.7, 0.0, dst);
    imshow("图像展示 ", dst);
    waitKey(0);
}

位运算

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    Mat img1 = imread("image.jpg");
    Mat img2 = imread("mask.jpg");
    Mat roi = img1(Range(0, img2.rows), Range(0, img2.cols));
    Mat img2gray;
    cvtColor(img2, img2gray, COLOR_BGR2GRAY);

    Mat mask;
    threshold(img2gray, mask, 10, 255, THRESH_BINARY);
    Mat mask_inv, mask1;
    bitwise_not(mask, mask_inv);
    Mat img1_bg;
    bitwise_and(roi, roi, img1_bg, mask_inv);
    Mat img2_fg;
    bitwise_and(img2, img2, img2_fg, mask);
    Mat destination;
    add(img1_bg, img2_fg, destination);
    destination.copyTo(img1(Range(0, img2.rows), Range(0, img2.cols)));

    imshow("mask", mask);
    waitKey(0);
    imshow("mask_inv", mask_inv);
    waitKey(0);
    imshow("img1_bg", img1_bg);
    waitKey(0);
    imshow("img2_fg", img2_fg);
    waitKey(0);
    imshow("destination", destination);
    waitKey(0);
    imshow("img1", img1);
    waitKey(0);
    return 0;
}

使用OpenCV度量程序性能

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    double e1 = (double)getTickCount(); //单位:毫秒
    // 你的代码
    double e2= (double)getTickCount();
    double t = 1000 * (e2 - e1) / getTickFrequency(); // 代码用时(秒)
    return 0;
}

OpenCV中的默认优化

与Python同理

    std::cout << useOptimized()<<std::endl;
    setUseOptimized(false);
    std::cout << useOptimized() << std::endl;

代码:

https://gitee.com/BinaryAI/open-cv-c--and-python

参考:
[1] https://docs.opencv.org/4.5.5/index.html

相关文章
|
2月前
|
计算机视觉
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
这篇文章详细介绍了OpenCV库中的图像二值化函数`cv2.threshold`,包括二值化的概念、常见的阈值类型、函数的参数说明以及通过代码实例展示了如何应用该函数进行图像二值化处理,并展示了运行结果。
542 0
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
|
3月前
|
算法 计算机视觉
opencv图像形态学
图像形态学是一种基于数学形态学的图像处理技术,它主要用于分析和修改图像的形状和结构。
55 4
|
4月前
|
算法框架/工具 C++ Python
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
336 0
|
3月前
|
存储 计算机视觉
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
本文介绍了使用OpenCV进行图像读取、显示和存储的基本操作,以及如何绘制直线、圆形、矩形和文本等几何图形的方法。
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
|
2月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
519 3
|
2月前
|
计算机视觉 Python
python利用pyqt5和opencv打开电脑摄像头并进行拍照
本项目使用Python的PyQt5和OpenCV库实现了一个简单的摄像头应用。用户可以通过界面按钮打开或关闭摄像头,并实时预览视频流。点击“拍照”按钮可以捕捉当前画面并保存为图片文件。该应用适用于简单的图像采集和处理任务。
156 0
python利用pyqt5和opencv打开电脑摄像头并进行拍照
|
2月前
|
C++ Python
探索Python与C/C++混合编程的艺术
探索Python与C/C++混合编程的艺术
54 1
|
2月前
|
机器学习/深度学习 算法 计算机视觉
【Python篇】Python + OpenCV 全面实战:解锁图像处理与视觉智能的核心技能
【Python篇】Python + OpenCV 全面实战:解锁图像处理与视觉智能的核心技能
106 2
|
3月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
69 11
|
3月前
|
存储 计算机视觉 C++
在C++中实现Armadillo库与OpenCV库之间的数据格式转换
在C++中实现Armadillo库与OpenCV库之间的数据格式转换是一项常见且实用的技能。上述步骤提供了一种标准的方法来进行这种转换,可以帮助开发者在两个库之间高效地转移和处理数据。虽然转换过程相对直接,但开发者应留意数据类型匹配和性能优化等关键细节。
32 3