2015年,Gatsys等人在論文A Neural Algorithm of Artistic Style中提出了最初的神經風格遷移算法。2016年,Johnson等人發表了Perceptual Losses for Real-Time Style Transfer and Super-Resolutioin一文,將神經網絡遷移作為用感知損失處理超解析度問題的框架。結果表明該算法比Gatys等人的方法快了三倍。接下來,我將介紹如何在自己的圖像和視頻流中應用神經風格遷移。
首先說明的一點是,今天討論的方法在一個CPU上可以達到近乎實時的效果,如果在GPU上則完全可以實現實時效果。
首先我們會簡單塔倫下什麼是神經風格遷移,以及它是如何運作的。之後我們會用OpenCV和Python動手操作。
從左至右:我們的內容圖像;風格圖像;輸出的風格遷移圖像
神經風格遷移主要有兩個過程:
上圖就是將梵谷著名的畫作《星夜》的風格應用到普通的生活照上,我們保留了原照片中的山、人物和啤酒等所有內容,但全部替換成了梵谷的油畫風格。
問題就是,我們應該如何定義一個神經網絡,讓它執行神經風格遷移呢?
在Gatys等人提出的首篇論文中,神經風格遷移算法不需要新的架構。相反,我們可以用一個預訓練網絡(通常在ImageNet上進行的預訓練),並且定義一個損失函數,能讓我們達到風格遷移的目標,然後對損失函數不斷優化。
那麼,這裡的問題就不是「該用什麼神經網絡」了,而是「該用什麼損失函數」。
答案包括:內容損失、風格損失和總變差損失。每個部分都是單獨計算,然後在一個元損失函數中結合。通過將元損失函數最小化,我們將依次對內容、風格和總變差損失進行優化。
雖然Gatys等人的方法能生成不錯的神經風格遷移結果,但是它的速度非常慢。2016年,Johnson等人在Gatys的基礎上提出的全新算法速度快了三倍,但同時也存在著缺點,即用戶不能隨機選擇想要應用的風格圖像。用戶首先要訓練一個網絡,生成你想要的風格。網絡訓練好後,你可以將它應用到任意內容圖像上。
然而到了2017年,Ulyanov等人發表了Instance Normalization: The Missing Ingredient for Fast Stylization一文,他們表示將batch normalization替換成instance normalization(然後在訓練和測試時都應用instance normalization),可以達到更快的效果,並且藝術效果也更好。
在開始今天的教程前,請先下載我提供的資料(點擊文末原文地址獲取資料)。準備好了腳本、模型和圖像後,你可以用tree指令檢查項目的結構:
1$ tree --dirsfirst
2.
3├── images
4│ ├── baden_baden.jpg
5│ ├── giraffe.jpg
6│ ├── jurassic_park.jpg
7│ └── messi.jpg
8├── models
9│ ├── eccv16
10│ │ ├── composition_vii.t7
11│ │ ├── la_muse.t7
12│ │ ├── starry_night.t7
13│ │ └── the_wave.t7
14│ └── instance_norm
15│ ├── candy.t7
16│ ├── feathers.t7
17│ ├── la_muse.t7
18│ ├── mosaic.t7
19│ ├── starry_night.t7
20│ ├── the_scream.t7
21│ └── udnie.t7
22├── neural_style_transfer.py
23├── neural_style_transfer_examine.py
24└── neural_style_transfer_video.py
如果你從下載了.zip文件,就無需上網找其他素材了。我在其中提供了很多測試用的圖像和模型。同時還有三種Python腳本。
接下來讓我們用OpenCV和Python進行神經風格遷移的實踐。
首先打開neural_style_transfer.py文件,插入如下代碼:
1# import the necessary packages
2import argparse
3import imutils
4import time
5import cv2
6
7# construct the argument parser and parse the arguments
8ap = argparse.ArgumentParser()
9ap.add_argument("-m", "--model", required=True,
10 help="neural style transfer model")
11ap.add_argument("-i", "--image", required=True,
12 help="input image to apply neural style transfer to")
13args = vars(ap.parse_args())
首先,我們導入所需的包並解析命令行參數。
導入的有:
該腳本下需要兩個命令行:
你不需要改變命令行代碼,參數會在運行過程中進行處理。如果你不熟悉這一過程,可以閱讀我另一篇文章:
www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/
接下來的部分比較有趣,我們要下載圖像和模型,然後計算神經風格遷移:
15# load the neural style transfer model from disk
16print("[INFO] loading style transfer model...")
17net = cv2.dnn.readNetFromTorch(args["model"])
18
19# load the input image, resize it to have a width of 600 pixels, and
20# then grab the image dimensions
21image = cv2.imread(args["image"])
22image = imutils.resize(image, width=600)
23(h, w) = image.shape[:2]
24
25# construct a blob from the image, set the input, and then perform a
26# forward pass of the network
27blob = cv2.dnn.blobFromImage(image, 1.0, (w, h),
28 (103.939, 116.779, 123.680), swapRB=False, crop=False)
29net.setInput(blob)
30start = time.time()
31output = net.forward()
32end = time.time()
在這部分代碼中,我們進行了:
接下來,重要的是對輸出圖像進行後處理:
34# reshape the output tensor, add back in the mean subtraction, and
35# then swap the channel ordering
36output = output.reshape((3, output.shape[2], output.shape[3]))
37output[0] += 103.939
38output[1] += 116.779
39output[2] += 123.680
40output /= 255.0
41output = output.transpose(1, 2, 0)
最後一步是將輸出圖像顯示在屏幕上:
43# show information on how long inference took
44print("[INFO] neural style transfer took {:.4f} seconds".format(
45 end - start))
46
47# show the images
48cv2.imshow("Input", image)
49cv2.imshow("Output", output)
50cv2.waitKey(0)
當你下載好文件後,打開終端執行以下命令:
1$ python neural_style_transfer.py --image images/giraffe.jpg \
2 --model models/eccv16/the_wave.t7
3[INFO] loading style transfer model...
4[INFO] neural style transfer took 0.3152 seconds
現在,對命令行參數做簡單改變,然後用《侏羅紀公園》中的截圖作為內容圖像,進行風格遷移:
1$ python neural_style_transfer.py --image images/jurassic_park.jpg \
2 --model models/instance_norm/the_scream.t7
3[INFO] loading style transfer model...
4[INFO] neural style transfer took 0.1202 seconds
另一個例子:
1$ python neural_style_transfer.py --image images/messi.jpg \
2 --model models/instance_norm/udnie.t7
3[INFO] loading style transfer model...
4[INFO] neural style transfer took 0.1495 seconds
這是我最喜歡的案例,感覺都能當做酒吧的裝飾畫了。
上面我們講了如何在單一圖像上應用風格遷移,現在我們要把這一過程放在視頻上。
大致流程和圖像處理差不多,在這一腳本中,我們將:
利用一個特殊的Python迭代器,它可以讓我們在模型路徑中循環使用所有可用的神經風格遷移模型。
啟動網絡攝像頭視頻流,我們會(近乎)實時處理攝像頭的幀。對於某些較大的模型,系統可能會慢一些。
在每一幀上應用風格遷移,對輸出進行後處理,並將結果顯示在屏幕上。
如果用戶按下「n」鍵,我們將把迭代器循環運用到下一個神經風格遷移模型上,不用重啟腳本。
首先,打開neural_style_transfer_video.py文件,插入以下代碼:
1# import the necessary packages
2from imutils.video import VideoStream
3from imutils import paths
4import itertools
5import argparse
6import imutils
7import time
8import cv2
9
10# construct the argument parser and parse the arguments
11ap = argparse.ArgumentParser()
12ap.add_argument("-m", "--models", required=True,
13 help="path to directory containing neural style transfer models")
14args = vars(ap.parse_args())
之後,創建模型路徑迭代器:
16# grab the paths to all neural style transfer models in our 'models'
17# directory, provided all models end with the '.t7' file extension
18modelPaths = paths.list_files(args["models"], validExts=(".t7",))
19modelPaths = sorted(list(modelPaths))
20
21# generate unique IDs for each of the model paths, then combine the
22# two lists together
23models = list(zip(range(0, len(modelPaths)), (modelPaths)))
24
25# use the cycle function of itertools that can loop over all model
26# paths, and then when the end is reached, restart again
27modelIter = itertools.cycle(models)
28(modelID, modelPath) = next(modelIter)
一旦我們開始在while循環中處理幀,「n」按鍵就會在迭代器中下載「下一個」模型。
為了創建模型迭代器,我們:
讓我們開始下載第一個模型並對視頻進行處理:
30# load the neural style transfer model from disk
31print("[INFO] loading style transfer model...")
32net = cv2.dnn.readNetFromTorch(modelPath)
33
34# initialize the video stream, then allow the camera sensor to warm up
35print("[INFO] starting video stream...")
36vs = VideoStream(src=0).start()
37time.sleep(2.0)
38print("[INFO] {}. {}".format(modelID + 1, modelPath))
在32行,我們讀取了第一個模型利用的路徑。在36和37行,啟動了視頻,從攝像頭中採集幀。
之後在幀與幀之間進行循環:
40# loop over frames from the video file stream
41while True:
42 # grab the frame from the threaded video stream
43 frame = vs.read()
44
45 # resize the frame to have a width of 600 pixels (while
46 # maintaining the aspect ratio), and then grab the image
47 # dimensions
48 frame = imutils.resize(frame, width=600)
49 orig = frame.copy()
50 (h, w) = frame.shape[:2]
51
52 # construct a blob from the frame, set the input, and then perform a
53 # forward pass of the network
54 blob = cv2.dnn.blobFromImage(frame, 1.0, (w, h),
55 (103.939, 116.779, 123.680), swapRB=False, crop=False)
56 net.setInput(blob)
57 output = net.forward()
接著進行後處理並將輸出圖像展示出來:
59# reshape the output tensor, add back in the mean subtraction, and
60 # then swap the channel ordering
61 output = output.reshape((3, output.shape[2], output.shape[3]))
62 output[0] += 103.939
63 output[1] += 116.779
64 output[2] += 123.680
65 output /= 255.0
66 output = output.transpose(1, 2, 0)
67
68 # show the original frame along with the output neural style
69 # transfer
70 cv2.imshow("Input", frame)
71 cv2.imshow("Output", output)
72 key = cv2.waitKey(1) & 0xFF
對按鍵的處理:
74# if the `n` key is pressed (for "next"), load the next neural
75 # style transfer model
76 if key == ord("n"):
77 # grab the next neural style transfer model model and load it
78 (modelID, modelPath) = next(modelIter)
79 print("[INFO] {}. {}".format(modelID + 1, modelPath))
80 net = cv2.dnn.readNetFromTorch(modelPath)
81
82 # otheriwse, if the `q` key was pressed, break from the loop
83 elif key == ord("q"):
84 break
85
86# do a bit of cleanup
87cv2.destroyAllWindows()
88vs.stop()
兩種不同的按鍵會對腳本運行產生不同的影響:
執行以下命令就可以在視頻上運用風格遷移啦:
1$ python neural_style_transfer_video.py --models models
可以看到,只需要按一個按鍵就能輕鬆地進行循環。下面是我自己做的demo視頻:
今天的教程是教大家如何用OpenCV和Python在圖片和視頻上運用神經風格遷移。具體來說,我們用的模型是Johnson等人於2016年提出的,你可以在我提供的連結中下載。希望這篇教程對你有用!
想要了解更多資訊,請掃描下方二維碼,關注機器學習研究會
轉自: 新智元