☞ ░ 前往老猿Python博文目录 https://blog.csdn.net/LaoYuanPython ░
一、引言
前一阵子家人报考教师资格证考试,因报名需要将蓝底的数字相片换成白底的,老猿虽然在学习图像处理相关开发技术,但并没有熟练使用的图像编辑软件,一般也就是用个windows的画图工具简单处理一下,因此这个事情就只好求助于OpenCV的图像处理技术。不过老猿OpenCV图像处理也就学了最低级的图像处理,其他的还没学完,也就会图像空间变换、腐蚀和膨胀处理、阈值处理、几何变换、四则运算、鼠标键盘事件等。具体请参考《OpenCV-Python图形图像处理》。
二、处理思路
2.1、初步思路
老猿试图用学过的图像处理基础知识能完成数字图像的处理,大致构思如下:
- 先将图像分离为R、G、B三通道的三个图像,蓝底应该是R、G分量小B分量较大的部分,分别对这三部分进行阈值处理(R、G分量小于某个值,B分量要求大于某个值),满足这样条件的我们就认为该像素对应区域为背景色区域,阈值处理后可以得到背景的掩膜图像A以及其反转后对应前景色的掩膜图像F;
- 将原照片自身与自身相乘,相乘时设置其掩膜为掩膜图像F,得到照片前景;
- 构造一与照片大小相同的纯白色(新底色)图像,将其自身与自身相乘,相乘时设置其掩膜为掩膜图像A,得到对应的新底色背景;
- 将新得到的照片前景和新底色背景相加或相或得到新底色的照片。
2.3、实际处理遇到的问题及应对
实际处理时,发现远比这个复杂,有2个问题:
- 照片的背景色不是单一的像素值,而是不同位置会有不同,特别是前景色周边的灰度值与其他部分差异较大;
- 背景色的灰度值与前景色的灰度值不是简单的大于等于或小于等于关系,而是部分前景色小于背景色灰度值,部分前景色灰度值大于背景色灰度值,甚至有部分灰度值前景色和背景色是相同的。
在这个时候就需要根据实际相片底色情况调整RGB三个分量阈值处理的阈值,需要能对未能有效识别为背景区域的背景区域方便获取对应像素值。同时为了解决边界问题,可能需要进行背景色的膨胀处理。
2.4、代码实现
按照以上处理思路和问题应对方式,老猿实现了一个图像鼠标点击获取像素值输出的函数和一个图像背景色替换的函数,代码如下:
import cv2
import numpy as np
from opencvPublic import readImgFile #类似imread的图片文件装载函数,支持识别中文文件名
def OnMouseEvent( event, x, y, flags,img):#图片点击时输出对应位置和像素值
#鼠标左键按下打印对应位置的图像的像素值
if event==cv2.EVENT_LBUTTONDOWN:
print(f'{(x,y)}:{img[y][x]}')
def changePhotoBG(fileName,BGRThresh,MorphOpCount=0):
photo = readImgFile(fileName) #读入图像
BThresh,GThresh,RThresh = BGRThresh
bgWhite = np.full(photo.shape[:],255,dtype=np.uint8) #构造一个与图像大小完全相同的全白图像
#分离读入图像的rgb分量,并对每个分量进行阈值处理,确认每个分量的阈值是最关键的一步,与具体图像背景紧密相关,根据背景调整相关阈值
b,g,r = cv2.split(photo)
ret, maskb = cv2.threshold(b, BThresh, 255, cv2.THRESH_BINARY)
ret,maskg = cv2.threshold(g, GThresh, 255, cv2.THRESH_BINARY)
ret, maskr = cv2.threshold(r,RThresh, 255, cv2.THRESH_BINARY)
#因要替换蓝色底,因此需要判断哪些像素是蓝底像素,判断时,要求经过阈值处理的G、R分量为0,B分量为255
# 把符合此要求的灰度图作为处理图像的掩膜就能得到输入图像的背景色区域
#如果要处理非蓝底的,则需要进行下面两行代码的修改将非底色的其他两个颜色的掩膜相或后求反,再与底色的掩膜相与
maskrg = cv2.bitwise_not(cv2.bitwise_or(maskr,maskg))
maskbgr = cv2.bitwise_and(maskb,maskrg)
#由于在边界位置可能存在像素值情况可能与其他背景色不同的情况,因此可能需要进行背景掩膜的扩展膨胀(MORPH_DILATE)处理,且迭代次数可能也有不同,视具体图像而定
if MorphOpCount:#背景色掩膜进行扩张处理
kernal = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
maskbgr = cv2.morphologyEx(maskbgr, cv2.MORPH_DILATE , kernal, iterations=MorphOpCount)
#获得背景掩膜的反图像得到前景掩膜
maskbgrInv = cv2.bitwise_not(maskbgr)
#获取背景掩膜对应的白色背景对应图像
bgWhite = cv2.bitwise_and(bgWhite, bgWhite, mask=maskbgr)
#将输入图像应用前景掩膜,得到输入图像的前景,并将该前景与掩膜处理后的白色背景相叠加获得最后处理图像
photoFront = cv2.bitwise_and(photo,photo,mask=maskbgrInv)
result = cv2.bitwise_or(photoFront,bgWhite)
#显示处理图像并设置鼠标回调函数,当出现未完全处理好的噪点时,通过鼠标获得该噪点位置的像素值,以调整前面的阈值处理的阈值
cv2.imshow('pic',result)
cv2.setMouseCallback('pic', OnMouseEvent,result)
cv2.waitKey(0)
使用的自定义公用模块函数readImgFile,其功能请参考《https://blog.csdn.net/LaoYuanPython/article/details/111351901 OpenCV-Python图形图像处理:自用的一些工具函数功能及调用语法介绍》中的介绍。
三、测试过程
3.1、处理图片说明
本次介绍以百度“蓝底相片”找到的图片(如果涉及侵权请博客留言处理)作为处理对象介绍:
2、获取图像的背景色的阈值
怎么来确认R、G、B的阈值呢?网上有蓝底相片RGB值的多种说法,一方面太过理想化,另一方面说法还矛盾,怎么办呢?其实很简单,相片的左上角一般为底色,将图像加载后,看前几个像素的R、G、B值,让其作为参考阈值上下浮动一下。上述照片的前几个像素值如下:
可以看到前几个像素的B、G、R分量中B大于等于238、G小于等于115、R小于等于57,注意阈值处理函数在type为THRESH_BINARY时是小于等于阈值时为0,否则为指定最大值。
3、以前几个像素值作为参考来设定阈值
我们以B、G、R分别设置为237、115、57来设置阈值,不进行膨胀处理来调用上面实现的changePhotoBG函数来看看效果。
执行:changePhotoBG(r'f:\pic\girl.jpg',(237,115,57))
处理效果:
可以看到在涉及图像边界时处理效果不好,需要叠加背景色的扩展处理,对应调用为:
changePhotoBG(r'f:\pic\girl.jpg',(237,115,57),1)
changePhotoBG(r'f:\pic\girl.jpg',(237,115,57),2)
changePhotoBG(r'f:\pic\girl.jpg',(237,115,57),3)
changePhotoBG(r'f:\pic\girl.jpg',(237,115,57),5)
下图四张照片从上到下、从左到右对应分别做了1、2、3、5次膨胀后的结果图像:
可以看到膨胀5次以后基本上正常了,但还有一点点边界蓝色,再膨胀的话图片人像就会失真。
点击上述图片蓝色地方,看到输出的像素值如下:
(229, 266):[237 123 62]
(263, 116):[235 117 65]
(104, 269):[207 116 77]
因此根据这些输出调整BGR三个分量的值,将调用改为:changePhotoBG(r'f:\pic\girl.jpg',(206,123,77),3)
,可以得到如下图像:
可以看到效果相当不错了,老猿就没有再调整了。
四、小结
本文介绍了基于BGR颜色空间给蓝底照片换底的实现思路及程序代码,并将相关代码做成了一个比较通用的函数,只需要根据照片的背景色调整底色识别的B、G、R三个分量的阈值,就可以适应不同蓝色的背景色的照片情况,如果要处理非蓝底的,就需要将函数中求背景掩膜的两行代码相应进行调整。
本文代码测试完成并写完博文后,在网上找到了一篇类似功能介绍的博文,不过是基于HSV空间的,因此在标题中特地加上“基于RGB颜色空间”。过2天老猿将基于HSV的实现方式也写出,并提供参考博文对照一下。大家可以这2种颜色空间的实现方式都参考一下。
更多图像处理的内容请参考专栏《OpenCV-Python图形图像处理 https://blog.csdn.net/laoyuanpython/category_9979286.html》、《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》及《图像处理基础知识》的介绍。
如对文章内容存在疑问,可在博客评论区留言,或关注博客左边的:老猿Python 微信公号发消息咨询。
关于老猿的付费专栏
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_9607725.html 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 https://blog.csdn.net/LaoYuanPython/article/details/107580932 使用PyQt开发图形界面Python应用专栏目录》;
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10232926.html moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/107574583 moviepy音视频开发专栏文章目录》;
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》为《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/109713407 OpenCV-Python初学者疑难问题集专栏目录 》
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10762553.html Python爬虫入门 》站在一个互联网前端开发小白的角度介绍爬虫开发应知应会内容,包括爬虫入门的基础知识,以及爬取CSDN文章信息、博主信息、给文章点赞、评论等实战内容。
前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。
对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.csdn.net/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。