diff --git a/.gitignore b/.gitignore index fe34cbc8d99344e8365e9a40f7829bff95b0b2f5..21b20e3a9a826b6fff12cdd3e8417c63dbe9cd1b 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,10 @@ result/ /OneImage2Video/pixel_imgs/university +/NikeMouth/media +/NikeMouth/test_media +/NikeMouth/test.py + *.mp4 *.flv *.mp3 diff --git a/NikeMouth/imgs/nike.png b/NikeMouth/imgs/nike.png new file mode 100644 index 0000000000000000000000000000000000000000..8fce2355ae071f6d186f3ff787d1bcd290dfad2c Binary files /dev/null and b/NikeMouth/imgs/nike.png differ diff --git a/NikeMouth/imgs/test.jpg b/NikeMouth/imgs/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8738903d11ae3c893edcd328faa3649d30c15985 Binary files /dev/null and b/NikeMouth/imgs/test.jpg differ diff --git a/NikeMouth/main.py b/NikeMouth/main.py new file mode 100644 index 0000000000000000000000000000000000000000..e83840c64ea8ec9d960885b213366482ece69b99 --- /dev/null +++ b/NikeMouth/main.py @@ -0,0 +1,40 @@ +import os +import sys +import numpy as np +import cv2 +import normal2nike +sys.path.append("..") +from Util import util,ffmpeg +from options import Options +opt = Options().getparse() + +util.file_init(opt) + +if os.path.isdir(opt.media): + files = util.Traversal(opt.media) +else: + files = [opt.media] + +for file in files: + img = cv2.imread(file) + h,w = img.shape[:2] + if opt.output == 'image': + img = normal2nike.convert(img,opt.size,opt.intensity,opt.aspect_ratio,opt.ex_move,opt.mode) + cv2.imwrite(os.path.join(opt.result_dir,os.path.basename(file)), img) + elif opt.output == 'video': + frame = int(opt.time*opt.fps) + for i in range(frame): + tmp = normal2nike.convert(img,opt.size,i*opt.intensity/frame,opt.aspect_ratio, + opt.ex_move,opt.mode)[:4*(h//4),:4*(w//4)] + cv2.imwrite(os.path.join('./tmp/output_imgs','%05d' % i+'.jpg'), tmp) + cv2.imwrite(os.path.join(opt.result_dir,os.path.basename(file)), tmp) + ffmpeg.image2video( + opt.fps, + './tmp/output_imgs/%05d.jpg', + None, + os.path.join(opt.result_dir,os.path.splitext(os.path.basename(file))[0]+'.mp4')) + +# cv2.namedWindow('image', cv2.WINDOW_NORMAL) +# cv2.imshow('image',img) +# cv2.waitKey(0) +# cv2.destroyAllWindows() \ No newline at end of file diff --git a/NikeMouth/normal2nike.py b/NikeMouth/normal2nike.py new file mode 100644 index 0000000000000000000000000000000000000000..66f69c0dae91c957fca5f781bfdd7e7e7d6fa8e3 --- /dev/null +++ b/NikeMouth/normal2nike.py @@ -0,0 +1,147 @@ +import numpy as np +import cv2 +import face_recognition + + +# 繪製delaunay triangles +def draw_delaunay(img, TriangleList, delaunary_color): + + size = img.shape + r = (0, 0, size[1], size[0]) + + for t in TriangleList: + pt1 = (t[0], t[1]) + pt2 = (t[2], t[3]) + pt3 = (t[4], t[5]) + + # if rect_contains(r, pt1) and rect_contains(r, pt2) and rect_contains(r, pt3): + cv2.line(img, pt1, pt2, delaunary_color, 1) + cv2.line(img, pt2, pt3, delaunary_color, 1) + cv2.line(img, pt3, pt1, delaunary_color, 1) + +def get_Ms(t1,t2): + Ms = [] + for i in range(len(t1)): + pts1 = np.array([[t1[i][0],t1[i][1]],[t1[i][2],t1[i][3]],[t1[i][4],t1[i][5]]]).astype(np.float32) + pts2 = np.array([[t2[i][0],t2[i][1]],[t2[i][2],t2[i][3]],[t2[i][4],t2[i][5]]]).astype(np.float32) + # print(pts1) + Ms.append(cv2.getAffineTransform(pts1,pts2)) + return Ms + +def delaunay(h,w,points): + subdiv = cv2.Subdiv2D((0,0,w,h)) + for i in range(len(points)): + subdiv.insert(tuple(points[i])) + TriangleList = subdiv.getTriangleList() + return TriangleList + +def get_borderpoints(img): + h,w = img.shape[:2] + h,w = h-1,w-1 + points = [[0,0],[w//2,0],[w,0],[w,h//2],[w,h],[w//2,h],[0,h],[0,h//2]] + return np.array(points) + +def get_masks(img,t): + masks = np.zeros((len(t),img.shape[0],img.shape[1]), dtype=np.uint8) + for i in range(len(t)): + + points = np.array([[t[i][0],t[i][1]],[t[i][2],t[i][3]],[t[i][4],t[i][5]]]).astype(np.int64) + #print(points) + masks[i] = cv2.fillConvexPoly(masks[i],points,(255)) + cv2.line(masks[i], tuple(points[0]), tuple(points[1]), (255), 3) + cv2.line(masks[i], tuple(points[0]), tuple(points[2]), (255), 3) + cv2.line(masks[i], tuple(points[1]), tuple(points[2]), (255), 3) + + #masks[i] = cv2.drawContours(masks[i], points, contourIdx=0, color=255, thickness=2) + return masks + +def changeTriangleList(t,point,move): + t = t.astype(np.int64) + for i in range(len(t)): + if t[i][0]==point[0] and t[i][1]==point[1]: + t[i][0],t[i][1] = t[i][0]+move[0],t[i][1]+move[1] + elif t[i][2]==point[0] and t[i][3]==point[1]: + t[i][2],t[i][3] = t[i][2]+move[0],t[i][3]+move[1] + elif t[i][4]==point[0] and t[i][5]==point[1]: + t[i][4],t[i][5] = t[i][4]+move[0],t[i][5]+move[1] + return t + +def replace_delaunay(img,Ms,masks): + img_new = np.zeros_like(img) + h,w = img.shape[:2] + for i in range(len(Ms)): + # _img = img.copy() + mask = cv2.merge([masks[i], masks[i], masks[i]]) + mask_inv = cv2.bitwise_not(mask) + tmp = cv2.warpAffine(img,Ms[i],(w,h),borderMode = cv2.BORDER_REFLECT_101,flags=cv2.INTER_CUBIC) + tmp = cv2.bitwise_and(mask,tmp) + img_new = cv2.bitwise_and(mask_inv,img_new) + img_new = cv2.add(tmp,img_new) + return img_new + +def get_nikemouth_landmark(src_landmark,alpha=1,aspect_ratio=1.0,mode = 'only_mouth'): + + + nike = cv2.imread('./imgs/nike.png') + landmark = face_recognition.face_landmarks(nike)[0] + if mode == 'only_mouth': + src_landmark = src_landmark[56:] + nikemouth = np.array(landmark['top_lip']+landmark['bottom_lip']) + else: + src_landmark = src_landmark[25:] + nikemouth = np.array(landmark['left_eyebrow']+landmark['right_eyebrow']+landmark['nose_bridge']+\ + landmark['nose_tip']+landmark['left_eye']+landmark['right_eye']+landmark['top_lip']+\ + landmark['bottom_lip']) + + # 中心置0 + nikemouth = nikemouth-[np.mean(nikemouth[:,0]),np.mean(nikemouth[:,1])] + nikemouth[:,0] = nikemouth[:,0]*aspect_ratio + # 获取嘴巴大小 + nikemouth_h = np.max(nikemouth[:,1])-np.min(nikemouth[:,1]) + nikemouth_w = np.max(nikemouth[:,0])-np.min(nikemouth[:,0]) + src_h = np.max(src_landmark[:,1])-np.min(src_landmark[:,1]) + src_w = np.max(src_landmark[:,0])-np.min(src_landmark[:,0]) + + # 调整大小及位置 + beta = nikemouth_w/src_w + nikemouth = alpha*nikemouth/beta+[np.mean(src_landmark[:,0]),np.mean(src_landmark[:,1])] + return np.around(nikemouth,0) + + + +def convert(face,size=1,intensity=1,aspect_ratio=1.0,ex_move=[0,0],mode='all_face'): + + h,w = face.shape[:2] + landmark = face_recognition.face_landmarks(face)[0] + # print(landmark) + landmark_src = np.array(landmark['chin']+landmark['left_eyebrow']+landmark['right_eyebrow']+\ + landmark['nose_bridge']+landmark['nose_tip']+landmark['left_eye']+landmark['right_eye']+landmark['top_lip']+\ + landmark['bottom_lip']) + + landmark_src = np.concatenate((get_borderpoints(face), landmark_src), axis=0) + TriangleList_src = delaunay(h,w,landmark_src) + TriangleList_dst = TriangleList_src.copy() + + + nikemouth_landmark = get_nikemouth_landmark(landmark_src,alpha=size,aspect_ratio=aspect_ratio,mode = mode) + if mode == 'only_mouth': + for i in range(24): + move = ex_move+(nikemouth_landmark[i]-landmark_src[56+i])*intensity + TriangleList_dst = changeTriangleList(TriangleList_dst, landmark_src[56+i],move) + else: + for i in range(80-25): + move = ex_move+(nikemouth_landmark[i]-landmark_src[25+i])*intensity + TriangleList_dst = changeTriangleList(TriangleList_dst, landmark_src[25+i],move) + + Ms = get_Ms(TriangleList_src,TriangleList_dst) + masks = get_masks(face, TriangleList_dst) + face_new = replace_delaunay(face, Ms, masks) + + return face_new + +# # draw_delaunay(img, TriangleList, delaunary_color): + +# cv2.namedWindow('image', cv2.WINDOW_NORMAL) +# cv2.imshow('image',face_new) +# cv2.waitKey(0) +# cv2.destroyAllWindows() \ No newline at end of file diff --git a/NikeMouth/options.py b/NikeMouth/options.py new file mode 100644 index 0000000000000000000000000000000000000000..315b623003c76dbf5e2b9ffe7fc3fce70a6b79ab --- /dev/null +++ b/NikeMouth/options.py @@ -0,0 +1,30 @@ +import argparse + +class Options(): + def __init__(self): + self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + self.initialized = False + + def initialize(self): + self.parser.add_argument('-m','--media', type=str, default='./imgs/test.jpg',help='your image path or dir') + self.parser.add_argument('-mode','--mode', type=str, default='all_face',help='all_face | only_mouth') + self.parser.add_argument('-o','--output', type=str, default='image',help='output image or video') + self.parser.add_argument('-t','--time', type=float, default=2.0,help='time of video') + self.parser.add_argument('-f','--fps', type=float, default=25,help='fps of video') + self.parser.add_argument('-s','--size', type=float, default=1.0,help='size of mouth') + self.parser.add_argument('-i','--intensity', type=float, default=1.0,help='effect intensity') + self.parser.add_argument('-a','--aspect_ratio', type=float, default=1.0,help='aspect ratio of mouth') + self.parser.add_argument('-e','--ex_move', type=str, default='[0,0]',help='') + self.parser.add_argument('-r','--result_dir', type=str, default='./result',help='') + # self.parser.add_argument('--temp_dir', type=str, default='./tmp',help='') + + self.initialized = True + + def getparse(self): + if not self.initialized: + self.initialize() + self.opt = self.parser.parse_args() + + self.opt.ex_move = eval(self.opt.ex_move) + return self.opt + diff --git a/OneImage2Video/README_CN.md b/OneImage2Video/README_CN.md index 1ea91988ee141bfdcd937dddfcf92add0a05dd8d..fe4462bfb63962ebf6b1504daadb4aba4e4c2d02 100644 --- a/OneImage2Video/README_CN.md +++ b/OneImage2Video/README_CN.md @@ -1,37 +1,37 @@ -# OneImage2Video -用一个或一些图片生成视频
- -## 入门 -### 前提要求 -* Linux or Windows -* python3 -* [ffmpeg](http://ffmpeg.org/) -```bash -sudo apt-get install ffmpeg -``` -### 依赖 -代码依赖于 opencv-python, 可以通过 pip install安装 -```bash -pip install opencv-python -``` -### 克隆这个仓库 -```bash -git clone https://github.com/HypoX64/bilibili.git -cd bilibili/OneImage2Video -``` -### 运行程序 -* 在main.py中修改你的视频路径以及图片路径 -```python -# 在github看香蕉君 -# pixel_imgs_dir = './pixel_imgs/github' -# pixel_imgs_resize = 0 # resize pixel_imgs, if 0, do not resize -# output_pixel_num = 52 # how many pixels in the output video'width -# video_path = '../Video/素材/香蕉君/香蕉君_3.mp4' -# inverse = False -``` -* run -```bash -python main.py -``` - - +# OneImage2Video +用一个或一些图片生成视频
+ +## 入门 +### 前提要求 +* Linux or Windows +* python3 +* [ffmpeg](http://ffmpeg.org/) +```bash +sudo apt-get install ffmpeg +``` +### 依赖 +代码依赖于 opencv-python, 可以通过 pip install安装 +```bash +pip install opencv-python +``` +### 克隆这个仓库 +```bash +git clone https://github.com/HypoX64/bilibili.git +cd bilibili/OneImage2Video +``` +### 运行程序 +* 在main.py中修改你的视频路径以及图片路径 +```python +# 在github看香蕉君 +# pixel_imgs_dir = './pixel_imgs/github' +# pixel_imgs_resize = 0 # resize pixel_imgs, if 0, do not resize +# output_pixel_num = 52 # how many pixels in the output video'width +# video_path = '../Video/素材/香蕉君/香蕉君_3.mp4' +# inverse = False +``` +* run +```bash +python main.py +``` + + diff --git a/Util/ffmpeg.py b/Util/ffmpeg.py index 121ef8ad6ecb88fd0dde60b5e6c80fae93942f6b..bcd86c3c6b228873baf221003ec9e4c9fb864fe7 100644 --- a/Util/ffmpeg.py +++ b/Util/ffmpeg.py @@ -27,31 +27,36 @@ def run(args,mode = 0): return sout elif mode == 2: - p = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + cmd = args2cmd(args) + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) sout = p.stdout.readlines() return sout -def video2image(videopath, imagepath, fps=0, start_time=0, last_time=0): +def video2image(videopath, imagepath, fps=0, start_time='00:00:00', last_time='00:00:00'): args = ['ffmpeg', '-i', '"'+videopath+'"'] - if last_time!=0: + if last_time != '00:00:00': args += ['-ss', start_time] args += ['-t', last_time] if fps != 0: - args += ['-r', fps] + args += ['-r', str(fps)] args += ['-f', 'image2','-q:v','-0',imagepath] run(args) -def video2voice(videopath,voicepath,samplingrate=0): - args = ['ffmpeg', '-i', '"'+videopath+'"'] - if samplingrate != 0: - args += ['-ar', str(samplingrate)] +def video2voice(videopath, voicepath, start_time='00:00:00', last_time='00:00:00'): + args = ['ffmpeg', '-i', '"'+videopath+'"','-f mp3','-b:a 320k'] + if last_time != '00:00:00': + args += ['-ss', start_time] + args += ['-t', last_time] args += [voicepath] run(args) def image2video(fps,imagepath,voicepath,videopath): - os.system('ffmpeg -y -r '+str(fps)+' -i '+imagepath+' -vcodec libx264 '+'./tmp/video_tmp.mp4') - os.system('ffmpeg -i ./tmp/video_tmp.mp4 -i "'+voicepath+'" -vcodec copy -acodec aac '+videopath) + if voicepath != None: + os.system('ffmpeg -y -r '+str(fps)+' -i '+imagepath+' -vcodec libx264 '+'./tmp/video_tmp.mp4') + os.system('ffmpeg -i ./tmp/video_tmp.mp4 -i "'+voicepath+'" -vcodec copy -acodec aac '+videopath) + else: + os.system('ffmpeg -y -r '+str(fps)+' -i '+imagepath+' -vcodec libx264 '+videopath) def get_video_infos(videopath): args = ['ffprobe -v quiet -print_format json -show_format -show_streams', '-i', '"'+videopath+'"']