【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 − α ) f 0 ( x ) + α f 1 ( x ) g(x)=(1- \alpha ) f_{0} (x)+ \alpha f_{1} (x) g(x)=(1α)f0(x)+αf1(x)
通过将 α α α从0调到1、可以在一个图像到另一个图像之间执行渐变。

下面,我们将两张图像混合在一起。第一张图像的权重为0.3,第二张图像的权重为0.7。 cv.addWeighted()将以下等式应用于两张图像:
d s t = α ⋅ i m g 1 + β ⋅ i m g 2 + γ dst=\alpha\cdot img1+\beta\cdot img2+\gamma dst=αimg1+βimg2+γ
这里令 α = 0.3 , β = 0.7 , γ = 0 \alpha=0.3,\beta=0.7,\gamma=0 α=0.3,β=0.7,γ=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 ) (x,y) (x,y)为例:

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

intensity.val[0]包含一个从0到255的值(灰度图只有一个通道:0)。请注意 x x x y y 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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值