【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

相关文章
|
1月前
|
NoSQL 搜索推荐 openCL
【C/C++ 调试 GDB指南 】gdb调试基本操作
【C/C++ 调试 GDB指南 】gdb调试基本操作
58 2
|
1月前
|
BI 数据处理 索引
Pandas基本操作:Series和DataFrame(Python)
Pandas基本操作:Series和DataFrame(Python)
105 1
|
15天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
39 6
|
19天前
|
人工智能 机器人 C++
【C++/Python】Windows用Swig实现C++调用Python(史上最简单详细,80岁看了都会操作)
【C++/Python】Windows用Swig实现C++调用Python(史上最简单详细,80岁看了都会操作)
|
1月前
|
编译器 测试技术 C++
【Python 基础教程 01 全面介绍】 Python编程基础全攻略:一文掌握Python语法精髓,从C/C++ 角度学习Python的差异
【Python 基础教程 01 全面介绍】 Python编程基础全攻略:一文掌握Python语法精髓,从C/C++ 角度学习Python的差异
167 0
|
7天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python构建简单的图像识别应用
本文将介绍如何利用Python语言及其相关库来构建一个简单但功能强大的图像识别应用。通过结合OpenCV和深度学习模型,我们将展示如何实现图像的特征提取和分类,从而实现对图像中物体的自动识别和分类。无需复杂的算法知识,只需一些基本的Python编程技巧,你也可以轻松地创建自己的图像识别应用。
|
8天前
|
机器学习/深度学习 算法 自动驾驶
opencv python 图片叠加
【4月更文挑战第17天】
|
16天前
|
编解码 计算机视觉 Python
opencv 图像金字塔(python)
opencv 图像金字塔(python)
|
16天前
|
算法 Serverless 计算机视觉
opencv 直方图处理(python)
opencv 直方图处理(python)
|
24天前
|
C++ Python
【C++/Python】C++调用python文件
【C++/Python】C++调用python文件