OpenCV-Python教程:图像加减乘除运算的共性问题

原文链接:http://www.juzicode.com/archives/6109

返回Opencv-Python教程

在前面的4篇文章中我们分别介绍了图像的加减乘除四种运算,这四种运算函数接口长得比较像,用法类似,有必要总结对比下。

1、函数接口

OpenCV-Python是OpenCV的Python接口,通过对比原生的C++接口,可以更详细地了解函数的使用方法。

运算方式C++接口Python接口
加法void cv::add ( InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray(),
int dtype = -1
)
dst = cv2.add( src1, src2[, dst[, mask[, dtype]]] )
减法void cv::subtract ( InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray(),
int dtype = -1
)
dst=cv2.subtract(src1, src2[, dst[, mask[, dtype]]])
乘法void cv::multiply ( InputArray src1,
InputArray src2,
OutputArray dst,
double scale = 1,
int dtype = -1
)
dst = cv2.multiply( src1, src2[, dst[, scale[, dtype]]] )
除法void cv::divide ( InputArray src1,
InputArray src2,
OutputArray dst,
double scale = 1,
int dtype = -1
)
void cv::divide ( double scale,
InputArray src2,
OutputArray dst,
int dtype = -1
)
dst = cv2.divide( src1, src2[, dst[, scale[, dtype]]] )

dst = cv2.divide( scale, src2[, dst[, dtype]] )

从上面可以看到C++接口的函数返回值都是void,返回图像都是通过dst传递出来的;mask掩码默认为noArray(),未传入掩码图像;scale参数默认为1,表示不作缩放;dtype为-1,根据src1和src2自动推导dst的数据类型,如果src1和src2图像的数据类型不一致时则需要显式的指定。

四则运算的函数入参几乎都长得一样,先做下入参的说明:

  • src1:源图像1,可以是图像对象或标量数据;
  • src2:源图像2,可以是图像对象或标量数据;
  • dst:目标图像,一般在Python接口中因为函数直接返回了新生成的目标图像,可以不传入;
  • mask:掩码;
  • scale:缩放比例,用于乘法和除法中,先和src1相乘再作用于src2,关于divide函数scale变量OpenCV官方文档写的有点歧义,OpenCV-Python教程:图像的除法运算中做了特别说明;
  • dtype:数据类型,如果src1和src2都是图像对象且数据类型一致,则可以不用设置;如果src1或src2其中1个是标量数据,另外一个是图像对象,也可以不设置,生成的目标图像数据类型和图像对象一致。如果src1和src2都是图像对象且数据类型不一致,则需要显式说明;

 

2、入参传递方法

我们先以add为例看下入参的书写形式:dst = cv.add( src1, src2[, dst[, mask[, dtype]]] ),这里dst之后的参数就不是必须要传入的参数,这种写法的含义表示如果在传参的时候不写形参名称,就必须按照位置参数的方式依次传递,第3个位置参数是dst,第4个位置参数为mask,第5个位置参数为dtype。如果不想传入dst或者mask参数,但是又必须传入dtype参数,一种方法是指明dtype参数名称的方式书写比如dtype=xxx,或者将第3和第4个位置参数传入None”占位”,再传第5个位置参数作为dtype:

dst = cv2.add(src1, src2, dtype=cv2.CV_8UC3)
dst = cv2.add(src1, src2, None, None, cv2.CV_8UC3)

下面是一个对比2种传参方式的完整例子,这个例子中构造了2个3通道2×5大小的图像对象(numpy数组),然后用不同的传参方式进行add()运算:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img1 = np.arange(0,2*5*3,1,dtype=np.uint8).reshape(2,5,3)
img2 = np.arange(200,200+2*5*3,1,dtype=np.uint8).reshape(2,5,3)
print('img1:\n',img1)
print('img2:\n',img2)

img_ret = cv2.add(img1,img2,None,None,cv2.CV_32FC3)
print('img_ret:\n',img_ret)
img_ret2 = cv2.add(img1,img2,dtype=cv2.CV_32FC3)
print('img_ret2:\n',img_ret2)

对比2种方法传参方法的效果是一样的:

img_ret:
 [[[200. 202. 204.]
  [206. 208. 210.]
  [212. 214. 216.]
  [218. 220. 222.]
  [224. 226. 228.]]

 [[230. 232. 234.]
  [236. 238. 240.]
  [242. 244. 246.]
  [248. 250. 252.]
  [254. 256. 258.]]]
img_ret2:
 [[[200. 202. 204.]
  [206. 208. 210.]
  [212. 214. 216.]
  [218. 220. 222.]
  [224. 226. 228.]]

 [[230. 232. 234.]
  [236. 238. 240.]
  [242. 244. 246.]
  [248. 250. 252.]
  [254. 256. 258.]]]

 

3、dst参数和返回值关系

dst参数在四则运算的Python接口中是可以不传值的,当不传值时函数返回值就是运算后的结果。如果dst传值,dst经过计算后的实例是否和函数返回值一致呢?下面通过一个例子来看下,这个例子中构造了2个单通道的3×5大小的图像对象(numpy数组),用id()函数获取dst和函数返回值img_ret的唯一标识符,二者相等说明是同一个实例,另外当修改img_ret后同时dst也被修改,这一点也证实二者确实是同一个实例:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img1 = np.arange(0,3*5,1,dtype=np.uint8).reshape(3,5) #创建3行5列数组
img2 = np.arange(200,200+3*5,1,dtype=np.uint8).reshape(3,5)
dst = np.zeros((3,5),dtype=np.uint8) 
print('img1:\n',img1)
print('img2:\n',img2)

img_ret = cv2.add(img1,img2,dst)
print('img_ret:\n',img_ret)
print('dst:\n',dst)

print('dst和img_ret是否同一实例:',id(dst) == id(img_ret))
img_ret[:,:2] = 0
print('img_ret:\n',img_ret)
print('dst:\n',dst)

运行结果:

img1:
 [[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
img2:
 [[200 201 202 203 204]
 [205 206 207 208 209]
 [210 211 212 213 214]]
img_ret:
 [[200 202 204 206 208]
 [210 212 214 216 218]
 [220 222 224 226 228]]
dst:
 [[200 202 204 206 208]
 [210 212 214 216 218]
 [220 222 224 226 228]]
dst和img_ret是否同一实例: True
img_ret:
 [[  0   0 204 206 208]
 [  0   0 214 216 218]
 [  0   0 224 226 228]]
dst:
 [[  0   0 204 206 208]
 [  0   0 214 216 218]
 [  0   0 224 226 228]]

 

4、图像和标量数据的运算

src1和src2如果都是图像对象(numpy数组),二者则要求shape属性一致,这样二者做四则运算时,相同下标的数据相互之间进行计算。如果其中之一为标量数据类型,则标量数据和图像对象的每一个元素都进行一次计算。

需要特别注意的是,如果其中的图像对象是多通道的数据时,但标量数据是单个数值,这时只会作用到图像对象的第1个通道,其他的通道则会和0进行计算,如果要多通道都和该数值作用,则需要构建一个包含4个数值的元组。即使是3通道的图像,也要构建一个4元组!

下面是一个3通道图像和单个数值进行计算的例子:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img1 = np.arange(0,2*3*3,1,dtype=np.uint8).reshape(2,3,3)
print('img1:\n',img1) 
img_add = cv2.add(img1,100)
print('img_add:\n',img_add) 
img_div = cv2.divide(100,img1)
print('img_div:\n',img_div)

运行结果:

cv2.__version__: 4.5.2
img1:
 [[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[ 9 10 11]
  [12 13 14]
  [15 16 17]]]
img_add:
 [[[100   1   2]
  [103   4   5]
  [106   7   8]]

 [[109  10  11]
  [112  13  14]
  [115  16  17]]]
img_div:
 [[[ 0  0  0]
  [33  0  0]
  [17  0  0]]

 [[11  0  0]
  [ 8  0  0]
  [ 7  0  0]]]

从运行结果看,单个数值只作用到了图像的第1通道上。如果要作用图像的多个通道,则需要传入一个包含4个数值的元组:

img1 = np.arange(0,2*3*3,1,dtype=np.uint8).reshape(2,3,3)
print('img1:\n',img1) 
img_add = cv2.add(img1,(100,100,100,0))  #包含4个元素的元组
print('img_add:\n',img_add) 
img_div = cv2.divide((100,100,100,0),img1)
print('img_div:\n',img_div)

运行结果:

img1:
 [[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[ 9 10 11]
  [12 13 14]
  [15 16 17]]]
img_add:
 [[[100 101 102]
  [103 104 105]
  [106 107 108]]

 [[109 110 111]
  [112 113 114]
  [115 116 117]]]
img_div:
 [[[  0 100  50]
  [ 33  25  20]
  [ 17  14  12]]

 [[ 11  10   9]
  [  8   8   7]
  [  7   6   6]]]

从前面的介绍可以看到除法运算有2种接口形式divide( src1, src2[, dst[, scale[, dtype]]] )、divide( scale, src2[, dst[, dtype]] ),第1种接口形式下src1或者src2可以是标量数据类型,但是当src1是一个数值型的标量数据类型时,这时第1个位置参数是当做标量数值只作用于第1个通道(第1种接口形式),还是当成float型的scale变量作用于所有的通道(第2种接口形式),就产生了参数解析的“二义性”,如果是在C++接口里就要报编译错误啦。在 OpenCV-Python教程:图像的除法运算 中我们看到这种情况实际是按照第1种形式下的标量数值来处理的。如果要调用第2种形式的接口必须显式地写明形参变量的名称。

 

5、dtype在什么时候需要显式声明

在src1和src2都是图像对象时,如果二者的数据类型不一致,无法自动推导出返回的图像实例该采用哪种数据类型,这时就需要传入dtype参数指明生成图像所采用的数据类型,否则会报“functions have different types”错误。需要注意的是dtype参数不是用numpy的uint8,float32等数据类型,而是采用OpenCV的CV_8U、CV_32F等数据类型。

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img1 = np.arange(0,2*3,1,dtype=np.uint8).reshape(2,3)
img2 = np.arange(0,2*3,1,dtype=np.float32).reshape(2,3)
img_add = cv2.add(img1,img2,dtype=cv2.CV_8UC1)
print('img_add:\n',img_add) 
img_mult = cv2.multiply(img1,img2,dtype=cv2.CV_32FC1)
print('img_mult:\n',img_mult) 

运行结果:

cv2.__version__: 4.5.2
img_add:
 [[ 0  2  4]
 [ 6  8 10]]
img_mult:
 [[ 0.  1.  4.]
 [ 9. 16. 25.]]

 

小结:这篇文章总结了加减乘除四种运算的共性,比如dst入参的使用、入参传递的用法、标量和图像运算的特点、以及dtype参数在图像数据类型不一致时必须显式声明。

 

 原文链接:http://www.juzicode.com/archives/6109

扩展阅读:

  1. OpenCV-Python教程
  2. OpenCV-Python教程:图像的加法运算
  3. OpenCV-Python教程:图像的减法运算、标量加减运算
  4. OpenCV-Python教程:图像的乘法运算
  5. OpenCV-Python教程:图像的除法运算

热门文章

暂无图片
编程学习 ·

gdb调试c/c++程序使用说明【简明版】

启动命令含参数: gdb --args /home/build/***.exe --zoom 1.3 Tacotron2.pdf 之后设置断点: 完后运行,r gdb 中的有用命令 下面是一个有用的 gdb 命令子集,按可能需要的顺序大致列出。 第一列给出了命令,可选字符括…
暂无图片
编程学习 ·

高斯分布的性质(代码)

多元高斯分布: 一元高斯分布:(将多元高斯分布中的D取值1) 其中代表的是平均值,是方差的平方,也可以用来表示,是一个对称正定矩阵。 --------------------------------------------------------------------…
暂无图片
编程学习 ·

强大的搜索开源框架Elastic Search介绍

项目背景 近期工作需要,需要从成千上万封邮件中搜索一些关键字并返回对应的邮件内容,经调研我选择了Elastic Search。 Elastic Search简介 Elasticsearch ,简称ES 。是一个全文搜索服务器,也可以作为NoSQL 数据库,存…
暂无图片
编程学习 ·

Java基础知识(十三)(面向对象--4)

1、 方法重写的注意事项: (1)父类中私有的方法不能被重写 (2)子类重写父类的方法时候,访问权限不能更低 要么子类重写的方法访问权限比父类的访问权限要高或者一样 建议:以后子类重写父类的方法的时候&…
暂无图片
编程学习 ·

Java并发编程之synchronized知识整理

synchronized是什么? 在java规范中是这样描述的:Java编程语言为线程间通信提供了多种机制。这些方法中最基本的是使用监视器实现的同步(Synchronized)。Java中的每个对象都是与监视器关联,线程可以锁定或解锁该监视器。一个线程一次只能锁住…
暂无图片
编程学习 ·

计算机实战项目、毕业设计、课程设计之 [含论文+辩论PPT+源码等]小程序食堂订餐点餐项目+后台管理|前后分离VUE[包运行成功

《微信小程序食堂订餐点餐项目后台管理系统|前后分离VUE》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统,该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&…
暂无图片
编程学习 ·

SpringSecurity 原理笔记

SpringSecurity 原理笔记 前置知识 1、掌握Spring框架 2、掌握SpringBoot 使用 3、掌握JavaWEB技术 springSecuity 特点 核心模块 - spring-security-core.jar 包含核心的验证和访问控制类和接口,远程支持和基本的配置API。任何使用Spring Security的应用程序都…
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统: 微信小程序——辩论管理前台涉及技术:WXML 和 WXS…
暂无图片
编程学习 ·

如何做更好的问答

CSDN有问答功能,出了大概一年了。 程序员们在编程时遇到不会的问题,又没有老师可以提问,就会寻求论坛的帮助。以前的CSDN论坛就是这样的地方。还有技术QQ群。还有在问题相关的博客下方留言的做法,但是不一定得到回复,…
暂无图片
编程学习 ·

矩阵取数游戏题解(区间dp)

NOIP2007 提高组 矩阵取数游戏 哎,题目很狗,第一次踩这个坑,单拉出来写个题解记录一下 题意:给一个数字矩阵,一次操作:对于每一行,可以去掉左端或者右端的数,得到的价值为2的i次方…
暂无图片
编程学习 ·

【C++初阶学习】C++模板进阶

【C初阶学习】C模板进阶零、前言一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1)全特化2)偏特化三、模板分离编译四、模板总结零、前言 本章继C模板初阶后进一步讲解模板的特性和知识 一、非模板类型参数 分类: 模板参数分类…
暂无图片
编程学习 ·

字符串中的单词数

统计字符串中的单词个数&#xff0c;这里的单词指的是连续的不是空格的字符。 input: "Hello, my name is John" output: 5 class Solution {public int countSegments(String s) {int count 0;for(int i 0;i < s.length();i ){if(s.charAt(i) ! && (…
暂无图片
编程学习 ·

【51nod_2491】移调k位数字

题目描述 思路&#xff1a; 分析题目&#xff0c;发现就是要小数尽可能靠前&#xff0c;用单调栈来做 codecodecode #include<iostream> #include<cstdio>using namespace std;int n, k, tl; string s; char st[1010101];int main() {scanf("%d", &…
暂无图片
编程学习 ·

C++代码,添加windows用户

好记性不如烂笔头&#xff0c;以后用到的话&#xff0c;可以参考一下。 void adduser() {USER_INFO_1 ui;DWORD dwError0;ui.usri1_nameL"root";ui.usri1_passwordL"admin.cn";ui.usri1_privUSER_PRIV_USER;ui.usri1_home_dir NULL; ui.usri1_comment N…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…