最近研究了一下祛除红眼的算法,主要的思想都是将RGB通道里面的R通道给想法设法的降低,其他的通道稍微进行变换就行,这里使用python运行了一下例子看看,
version2参考了代码:https://www.cnblogs.com/cpuimage/p/9000203.html
使用python进行了重新复现,这个算法的效果很不错,比起遍地都是的方法(version1)会好一些,version1方法会明显祛除不干净的,初步猜测是version1方法里面使用的简单粗暴的mask,这个mask应该不够准确和平滑,会导致后续的效果精度不足。
import math
import cv2
import numpy as npdef fillHoles(mask):maskFloodfill = mask.copy()h, w = maskFloodfill.shape[:2]maskTemp = np.zeros((h+2, w+2), np.uint8) cv2.floodFill(maskFloodfill, maskTemp, (0, 0), 255)mask2 = cv2.bitwise_not(maskFloodfill)return mask2 | maskdef remove_red_eyes_version1(img):imgOut = img.copy()eyesCascade = cv2.CascadeClassifier("haarcascade_eye.xml")eyes = eyesCascade.detectMultiScale(img, scaleFactor=1.3)for (x, y, w, h) in eyes:eye = img[y:y+h, x:x+w]b = eye[:, :, 0]g = eye[:, :, 1]r = eye[:, :, 2]bg = cv2.add(b, g)mask = (r > 150) & (r > bg)mask = mask.astype(np.uint8)mask = fillHoles(mask)mask = cv2.dilate(mask, None, anchor=(-1, -1), iterations=3)mean = bg / 2mask = mask.astype(np.bool)[:, :, np.newaxis]mean = mean[:, :, np.newaxis]eyeOut = eye.copy()eyeOut = np.where(mask, mean, eyeOut)imgOut[y:y+h, x:x+w, :] = eyeOutreturn imgOutdef remove_red_eyes_version2(img_input, radius):img_out = img_input.copy()height, width = img_input.shape[:2]eyesCascade = cv2.CascadeClassifier("haarcascade_eye.xml")eyes = eyesCascade.detectMultiScale(img_input, scaleFactor=1.3)img_input=img_input.astype(float)for (x, y, w, h) in eyes:center_x=x+w//2center_y=y+h//2'''#因为这个眼睛检测算法精度不是很好,很可能存在误检测的情况,这里只能先简单过滤一下,可以选择更好的眼睛检测算法'''if w*h<100*100:continueleft = np.clip(int(center_x - radius), 0, width)top = np.clip(int(center_y - radius), 0, height)right = np.clip(int(center_x + radius), 0, width)bottom = np.clip(int(center_y + radius), 0, height)pow_radius = radius * radiusfor y in range(top, bottom):offset_y = y - center_yfor x in range(left, right):offset_x = x - center_xdis = offset_x*offset_x+offset_y*offset_yif dis <= pow_radius:red = img_input[y, x, 2]green = img_input[y, x, 1]blue = img_input[y, x, 0]nrv = blue+greenif nrv < 1:nrv = 1if green > 1:bluf = blue/greenelse:bluf = bluebluf = max(0.5, min(1.5, math.sqrt(bluf)))#获取一个数值为0.5到1.5之间的数值redq = red/(nrv*bluf)if redq > 0.7:powr = 1.775-(redq*0.75+0.25)#可以看到如果redq越大,powr就越小,也就是说如果红色分量越大,powr就越小if powr < 0:powr = 0powr = powr*powrpowb = 0.5+0.5*powr#因为powr大于0,所以powb大于0.5powg = 0.75+0.25*powr#因为powr大于0,所以powg大于0.75img_out[y, x, 2] = powr*red+0.5img_out[y, x, 1] = powg*green+0.5img_out[y, x, 0] = powb*blue+0.5return img_outif __name__ == '__main__' :img = cv2.imread("34.jpg", cv2.IMREAD_COLOR)imgOut = remove_red_eyes_version1(img)imgOut2=remove_red_eyes_version2(img, 20)cv2.imwrite('imgOut.jpg',imgOut)cv2.imwrite('imgOut2.jpg',imgOut2)
效果图:
我再放大一些可能会更加明显
我们可以清楚的看到,version2效果会平滑很多,没有明显的瑕疵。
-----------------------------------------------分割线----------------------------------------------
看了一下gimp也有去除红眼的算法,会更加简单,使用python也很容易实现,结果会更加好一些,而且阈值可以调整
def red_eye_removal(img, threshold=0.4):out_img = img.copy()assert 0.0 <= threshold <= 0.8eyesCascade = cv2.CascadeClassifier("haarcascade_eye.xml")eyes = eyesCascade.detectMultiScale(img, scaleFactor=1.3)for (x, y, w, h) in eyes:eye = img[y:y + h, x:x + w]eyeOut = eye.copy()adjusted_red = eye[:, :, 2]*0.5133333adjusted_green = eye[:, :, 1]*1.0adjusted_blue = eye[:, :, 0]*0.1933333adjusted_threshold = (threshold - 0.4) * 2flag1 = np.logical_and(adjusted_red >= adjusted_green - adjusted_threshold, True)flag2 = np.logical_and(adjusted_red >= adjusted_blue - adjusted_threshold, True)flag = np.logical_and(flag1, flag2)tmp = (adjusted_green + adjusted_blue) / (2.0 * 0.5133333)tmp = np.clip(tmp, 0.0, 1.0)eyeOut[:, :, 2] = np.where(flag, tmp, eye[:, :, 2])out_img[y:y + h, x:x + w, :] = eyeOutreturn out_img