第二十一节:美颜相关
- (一)亮度增强
- (二)磨皮美白(cv2.bilateralFilter)
- (三)瘦脸(局部平移算法)
- (四)红唇
- (五)放大眼睛(局部缩放算法)
- (六)备注
- (七)结语
好多天没有写博客了,这几天一直忙于炼丹,快被数据搞疯了。废话不多说,今天主要写美颜相关
(一)亮度增强
其实直方图均衡化也可以增加图像亮度,下面先用直方图均衡化的方式实现亮度的增强(以前已经写过灰度图的直方图均衡化,这里相当于彩图的均衡化)
import cv2
img= cv2.imread('face.jpg')
cv2.imshow('img', img)#这里展示一下原图
b,g,r=cv2.split(img)
b = cv2.equalizeHist(b)
g = cv2.equalizeHist(g)
r = cv2.equalizeHist(r)
out_put=cv2.merge((b,g,r))
cv2.imshow('out_put',out_put)
cv2.waitKey(0)
这里相当于分别对三通道进行图像均衡化
雀斑已经淡了很多了,也变白了。(个人觉的,opencv为啥不直接出以个彩图均衡化的函数)
这里使用另一种方法增加图像的亮度,而这种写法有两种
法一:
import cv2
import numpy as np
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars",650,47)
cv2.createTrackbar("light","TrackBars",0,100,empty)#这里设置light最大值为100,你也可以设置大一点
while True:
img= cv2.imread('face.jpg')
h,w=img.shape[:2]
#print(h,w)
cv2.imshow('img', img)#这里展示一下原图
light = cv2.getTrackbarPos("light", "TrackBars")
out_put=np.zeros((h,w,3),np.uint8)
for i in range(0,h):
for j in range(0,w):
b,g,r=img[i,j]
x=int(b)+light
y=int(g)+light
z=int(r)+light
if x>255:
x=255
if y>255:
y=255
if z>255:
z=255
out_put[i, j] = x, y, z
cv2.imshow('out_put',out_put)
cv2.waitKey(1)
法二:
import cv2
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars",650,47)
cv2.createTrackbar("light","TrackBars",0,100,empty)#这里设置light最大值为100
while True:
img= cv2.imread('face.jpg')
cv2.imshow('img', img)#这里展示一下原图
light = cv2.getTrackbarPos("light", "TrackBars")
b,g,r=cv2.split(img)
b=b+light#这里注意相加的值不要超过255
g=g+light#这里注意相加的值不要超过255
r=r+light#这里注意相加的值不要超过255
out_put=cv2.merge((b,g,r))
cv2.imshow('out_put',out_put)
cv2.waitKey(1)
这里明显右边的要白一点,雀斑也淡了一些
(二)磨皮美白(cv2.bilateralFilter)
这主要用到cv2.bilateralFilter双边滤波函数
参数 | 解释 |
src | 输入图像 |
d | 过滤时周围每个像素领域的直径 |
sigmaColor | 在color space中过滤sigma。参数越大,临近像素将会在越远的地方mix。 |
sigmaSpace | 在coordinate space中过滤sigma。参数越大,那些颜色足够相近的的颜色的影响越大。 |
看看代码吧:
import cv2
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars",650,150)
cv2.createTrackbar("d","TrackBars",0,255,empty)
cv2.createTrackbar("sigmaColor","TrackBars",0,255,empty)
cv2.createTrackbar("sigmaSpace","TrackBars",0,255,empty)
while True:
img= cv2.imread('face2.jpg')
cv2.imshow('img', img)#这里展示一下原图
d=cv2.getTrackbarPos("d","TrackBars")
sigmaColor=cv2.getTrackbarPos("sigmaColor", "TrackBars")
sigmaSpace=cv2.getTrackbarPos("sigmaSpace", "TrackBars")
output = cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace)#然而我不知道为什么,觉得这些雀斑挺可爱的
#output=cv2.bilateralFilter(img,15,35,35)#这里感觉这几个值效果较好,当然并不绝对
cv2.imshow('output',output)
cv2.waitKey(1)
这效果还是不错的,但不知为什么,我觉得这雀斑挺可爱的,好吧这时可能会有人说直男
(三)瘦脸(局部平移算法)
这里需要用到到前一节中人脸关键点定位(68点),瘦左脸,计算3号点到5号点的距离作为瘦脸最大参考距离,瘦右脸,计算13号点到15号点的距离作为瘦脸的最大参考距离。这里实现的是简易瘦脸,不同的图像的效果不一样,下面有Trackbar调节瘦脸的程度
#导入工具包
from scipy.spatial import distance
import numpy as np
import dlib
import cv2
import math
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
def bilinear_insert(image, new_x, new_y):
"""
双线性插值法
"""
w, h, c = image.shape
if c == 3:
x1 = int(new_x)
x2 = x1 + 1
y1 = int(new_y)
y2 = y1 + 1
part1 = image[y1, x1].astype(np.float) * (float(x2) - new_x) * (float(y2) - new_y)
part2 = image[y1, x2].astype(np.float) * (new_x - float(x1)) * (float(y2) - new_y)
part3 = image[y2, x1].astype(np.float) * (float(x2) - new_x) * (new_y - float(y1))
part4 = image[y2, x2].astype(np.float) * (new_x - float(x1)) * (new_y - float(y1))
insertValue = part1 + part2 + part3 + part4
return insertValue.astype(np.int8)
def local_traslation_warp(image, start_point, end_point, radius):
"""
局部平移算法
"""
radius_square = math.pow(radius, 2)
image_cp = image.copy()
dist_se = math.pow(np.linalg.norm(end_point - start_point), 2)
height, width, channel = image.shape
for i in range(width):
for j in range(height):
# 计算该点是否在形变圆的范围之内
# 优化,第一步,直接判断是会在(start_point[0], start_point[1])的矩阵框中
if math.fabs(i - start_point[0]) > radius and math.fabs(j - start_point[1]) > radius:
continue
distance = (i - start_point[0]) * (i - start_point[0]) + (j - start_point[1]) * (j - start_point[1])
if (distance < radius_square):
# 计算出(i,j)坐标的原坐标
# 计算公式中右边平方号里的部分
ratio = (radius_square - distance) / (radius_square - distance + dist_se)
ratio = ratio * ratio
# 映射原位置
new_x = i - ratio * (end_point[0] - start_point[0])
new_y = j - ratio * (end_point[1] - start_point[1])
new_x = new_x if new_x >= 0 else 0
new_x = new_x if new_x < height - 1 else height - 2
new_y = new_y if new_y >= 0 else 0
new_y = new_y if new_y < width - 1 else width - 2
# 根据双线性插值法得到new_x, new_y的值
image_cp[j, i] = bilinear_insert(image, new_x, new_y)
return image_cp
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 650, 70)
cv2.createTrackbar("dist_left", "TrackBars", 0, 100, empty) # 这里设置dist_left最大值为100
cv2.createTrackbar("dist_right", "TrackBars", 0, 100, empty) # 这里设置dist_right最大值为100
while True:
img=cv2.imread('1.jpg')
img1=img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测人脸
rects = detector(gray, 0)
# 遍历每一个检测到的人脸
for rect in rects:
# 获取坐标
result = predictor(gray, rect)
result=result.parts()
points = [[p.x, p.y] for p in result]
#print(type(points))#类型为list
points=np.array(points)#这里需将上面的points转化为数组
#print(points)
for (x, y) in points[:]:
# print(rects.parts())
cv2.circle(img1, (x, y), 3, (0, 255, 0), -1) # 还是原谅色
#这一块主要用来瘦脸
############################################################################################
#out_put = thin_face(img, points)
end_point = points[29] # 30号点
# 瘦左脸,计算3号点到5号点的距离作为瘦脸最大参考距离
dist_left = distance.euclidean(points[3], points[5])
print(dist_left)
dist_left = cv2.getTrackbarPos("dist_left", "TrackBars")
out_put = local_traslation_warp(img, points[3], end_point, dist_left)
# 瘦右脸,计算13号点到15号点的距离作为瘦脸的最大参考距离
dist_right = distance.euclidean(points[13], points[15])
print(dist_right)
dist_right = cv2.getTrackbarPos("dist_right", "TrackBars")
out_put = local_traslation_warp(out_put, points[14], end_point, dist_right)
############################################################################################
cv2.imshow("img", img)
cv2.imshow('img1',img1)
cv2.imshow("out_put",out_put)
cv2.waitKey(1)
上面分别是原图、人脸关键点的定位图、瘦脸结果图。虽然结果不明显,但是还是可以勉强可以看出下面的结果图的脸颊两侧要略瘦
(四)红唇
这个就比较简单了,找到嘴唇部分的关键点,然后凸包填充就可以了,当然只要你愿意,你可以对下面代码中red_lip()函数进行修改,对颜色及深度做更为细致的修改
import numpy as np
import dlib
import cv2
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
def red_lip(img, points, color): # 其实在里并不严谨,因为在里将牙齿也变红了,其所也不难,只要在扣嘴唇轮廓时将牙齿i轮廓给扣调就可以了
hull = cv2.convexHull(points) # 先计算嘴唇凸包(即获取嘴唇轮廓)
# 为了不影响图片效果,因为后面是将两张图片加权叠加,所以下面先生成和原图大小相同的图片,并在上面绘制嘴唇轮廓
mask = np.zeros_like(gray)
cv2.fillPoly(mask, np.array([hull]), 255)
roi = cv2.bitwise_and(gray, gray, mask=mask)
roi = np.stack((roi,) * 3, axis=-1)
roi = cv2.drawContours(roi, [hull], -1, (0, 0, color), -1)
cv2.imshow("roi", roi)
image = cv2.addWeighted(roi, 0.2, img.copy(), 0.9, 0)
return image
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 650, 70)
cv2.createTrackbar("color", "TrackBars", 0, 255, empty) # 这里设置color最大值为255
while True:
img = cv2.imread('input/1.png')
img1 = img.copy()
img2 = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测人脸
rects = detector(gray, 0)
# 遍历每一个检测到的人脸
for rect in rects:
# 获取坐标
result = predictor(gray, rect)
result = result.parts()
points = [[p.x, p.y] for p in result]
# print(type(points))#类型为list
points = np.array(points) # 这里需将上面的points转化为数组
# print(points)
for (x, y) in points[:]:
# print(rects.parts())
cv2.circle(img1, (x, y), 3, (0, 255, 0), -1) # 还是原谅色
# 嘴唇变红
color = cv2.getTrackbarPos("color", "TrackBars")
p = points[48:68] # 嘴唇点集分布
img2 = red_lip(img, p, color)
# color: 调节嘴唇的颜色深度
cv2.imshow("img", img)
cv2.imshow('img1', img1)
cv2.imshow("out_put", img2)
cv2.waitKey(1)
这个效果就很明显了,可能小伙伴们觉得颜色不够红,这里给小伙伴们提供一个方法就是,在增加一个TrackBar用来控制红色的透明度,小伙伴们还可以多添加几个TrackBar用来改变嘴唇的颜色,这里就不写了,交由小伙伴们自行发挥。
(五)放大眼睛(局部缩放算法)
这里我所以左右眼的最左点与最点的中点为中心进行缩放的,小伙伴们也可以用左右眼的上下斜对角点看看,其中下面代码中radius: 眼睛放大范围半径 strength:眼睛放大程度。
import numpy as np
import dlib
import cv2
import math
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
def bilinear_insert(image, new_x, new_y):
"""
双线性插值法
"""
w, h, c = image.shape
if c == 3:
x1 = int(new_x)
x2 = x1 + 1
y1 = int(new_y)
y2 = y1 + 1
part1 = image[y1, x1].astype(np.float) * (float(x2) - new_x) * (float(y2) - new_y)
part2 = image[y1, x2].astype(np.float) * (new_x - float(x1)) * (float(y2) - new_y)
part3 = image[y2, x1].astype(np.float) * (float(x2) - new_x) * (new_y - float(y1))
part4 = image[y2, x2].astype(np.float) * (new_x - float(x1)) * (new_y - float(y1))
insertValue = part1 + part2 + part3 + part4
return insertValue.astype(np.int8)
def local_zoom_warp(image, point, radius, strength):
"""
图像局部缩放算法
"""
height = image.shape[0]
width = image.shape[1]
left =int(point[0] - radius) if point[0] - radius >= 0 else 0
top = int(point[1] - radius) if point[1] - radius >= 0 else 0
right = int(point[0] + radius) if point[0] + radius < width else width-1
bottom = int(point[1] + radius) if point[1] + radius < height else height-1
radius_square = math.pow(radius, 2)
for y in range(top, bottom):
offset_y = y - point[1]
for x in range(left, right):
offset_x = x - point[0]
dist_xy = offset_x * offset_x + offset_y * offset_y
if dist_xy <= radius_square:
scale = 1 - dist_xy / radius_square
scale = 1 - strength / 100 * scale
new_x = offset_x * scale + point[0]
new_y = offset_y * scale + point[1]
new_x = new_x if new_x >=0 else 0
new_x = new_x if new_x < height-1 else height-2
new_y = new_y if new_y >= 0 else 0
new_y = new_y if new_y < width-1 else width-2
image[y, x] = bilinear_insert(image, new_x, new_y)
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 650, 70)
cv2.createTrackbar("radius", "TrackBars", 0, 100, empty) # 这里设置dist_left最大值为100
cv2.createTrackbar("strength", "TrackBars", 0, 100, empty) # 这里设置dist_right最大值为100
while True:
img=cv2.imread('1.jpg')
img1=img.copy()
img2=img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测人脸
rects = detector(gray, 0)
# 遍历每一个检测到的人脸
for rect in rects:
# 获取坐标
result = predictor(gray, rect)
result=result.parts()
points = [[p.x, p.y] for p in result]
#print(type(points))#类型为list
points=np.array(points)#这里需将上面的points转化为数组
#print(points)
for (x, y) in points[:]:
# print(rects.parts())
cv2.circle(img1, (x, y), 3, (0, 255, 0), -1) # 还是原谅色
# 以左眼最左点和最右点之间的中点为圆心
left_eye_top = points[42]
left_eye_bottom = points[46]
left_eye_center = (left_eye_top + left_eye_bottom) / 2
# 以右眼最左点和最右点之间的中点为圆心
right_eye_top = points[36]
right_eye_bottom = points[40]
right_eye_center = (right_eye_top + right_eye_bottom) / 2
# 放大双眼
radius=cv2.getTrackbarPos("radius", "TrackBars")
strength=cv2.getTrackbarPos("strength", "TrackBars")
local_zoom_warp(img2, left_eye_center, radius=radius, strength=strength)
local_zoom_warp(img2, right_eye_center, radius=radius, strength=strength)
#radius: 眼睛放大范围半径 strength:眼睛放大程度
cv2.imshow("img", img)
cv2.imshow('img1',img1)
cv2.imshow("out_put",img2)
cv2.waitKey(1)
这里小伙伴们可能看不出明显效果,但是效果还是有的,因为我在拖动Trackbars可以明显的看到眼睛变大的过程,当然这里面还有很多需要优化的地方,就不细说了
(六)备注
最近忙于炼丹,等后面有时间就开始写一些关于视觉方面的深度学习相关的例程,还有就是下次可能会写一个简单的头部姿态估计
(七)结语
学习opencv有很多的方法,我的建议是你可以加一些群,可以充分利用B站,CSDN,和百度。
在我的博客中,我不会讲解opencv的算法实现(当然我也不太会),我只会讲解一些函数的调用,不理解就多改一些参数,多尝试尝试,慢慢你就理解来。相信你总有一天可以说opencv不过“Ctrl+C,Crtl+V”
PS:如果有什么错误的地方,还请大家批评指正,不过错了也没啥关系,反正也没什么人看
最后,希望小伙伴们都能有所收获。