图像基本操作
由于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;
代码: