目錄
1.2 INPAINT_TELEA : Fast Marching Method based
本文將描述一類稱爲圖像修復的區域填充算法。想象一下找一張舊的家庭照片。你掃描它,它看起來很棒,除了一些劃痕。當然,你可以在photoshop中加載照片並修復劃痕。除此之外可以編寫10行代碼以使用OpenCV中的修復算法來解決問題。
1 什麼是圖像修復
圖像修復是計算機視覺中的一類算法,其目標是填充圖像或視頻內的區域。該區域使用二進制掩模進行標識,填充通常根據需要填充的區域邊界信息來完成。圖像修復的最常見應用是恢復舊的掃描照片。它還用於刪除圖像中的小的不需要的對象。
在本節中,我們將簡要討論在OpenCV中實現的兩種修復算法。
1.1 INPAINT_NS : Navier-Stokes based Inpainting
該方法於2001年發表在題爲Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting的論文。論文見:
http://www.math.ucla.edu/~bertozzi/papers/cvpr01.pdf
有時我覺得計算機視覺領域是一個來自其他領域的移民領域,如電子工程,計算機科學,物理和數學。他們將自己的想法帶到現場,以非常有趣和獨特的方式解決同樣的問題。電氣工程師可以將圖像看作2D信號,並應用信號處理理論來解決計算機視覺問題。另一方面,數學家可以將圖像看作連通圖並使用圖論解決計算機視覺問題。因此,爲流體動力學開發的理論也可以用於計算機視覺,這並不奇怪。在下圖中,我們的目標是填充暗區並獲得一個看起來像右邊的圖像。
我們如何填補這個黑色區域?我們想要的一個約束是邊緣進入點A應該連接邊緣離開點B。我們可能想要的另一個約束是連接A和B的曲線右邊的區域應該是白色,而左邊的區域應該是藍色的。
以上兩個約束基本上要求:保留漸變(即邊緣特徵)和繼續在平滑區域中傳播顏色信息。
作者建立了一個偏微分方程(PDE)來更新具有上述約束的區域內的圖像強度。
1.2 INPAINT_TELEA : Fast Marching Method based
該方法基於論文An Image Inpainting Technique Based on the Fast Marching Method。論文作者Alexandru Telea。論文見:
https://pdfs.semanticscholar.org/622d/5f432e515da69f8f220fb92b17c8426d0427.pdf
該方法實現使用不同的技術解決了相同的約束。作者不使用圖像拉普拉斯算子作爲平滑度的估計,而是使用像素的已知圖像鄰域上的加權平均值來補繪。已知的鄰域像素和梯度用於估計要修復的像素的顏色。
1.3 方法比較與函數實現
根據理論和論文,基於Navier-Stokes的修復應該更慢,並且傾向於產生比fast marching method的方法更模糊的結果。在實踐中,我們沒有發現這種情況。INPAINT_NS在我們的測試中產生了更好的結果,速度也略高於INPAINT_TELEA。
在OpenCV中,使用函數inpaint實現了修復算法。函數接口如下:
C++:
void inpaint( const Mat& src, const Mat& inpaintMask, Mat& dst, double inpaintRange, int flags );
Python:
dst = cv2.inpaint(src, inpaintMask, inpaintRadius, flags)
Src:源圖像
inpaintMask:二進制掩碼,指示要修復的像素。
Dst:結果圖像
inpaintRadius:表示修復的半徑
flags : 修復算法,主要有INPAINT_NS (Navier-Stokes based method) or INPAINT_TELEA (Fast marching based method)
2 結果與代碼
2.1 結果
讓我們來看看對林肯總統的歷史形象進行修復的結果。這張照片背後有一段引人入勝的歷史,我從維基百科借來的:
1865年2月5日星期日,在華盛頓特區的加德納畫廊,亞歷山大·加德納拍攝了幾張總統的多鏡頭照片。在本屆會議結束之前,加德納要求總統最後一個姿勢。他把相機拉得更近,拍了一張林肯頭部,肩膀和胸部的照片。神祕的玻璃板破裂。加德納小心翼翼地將它帶到了他的黑暗房間,並且能夠製作一張印刷品,但在林肯的臉上有一個不祥的裂縫。在這個印刷品完全破碎並被棄用。但這種印刷品,即O-118,至今仍然存在。多年來,許多人將這一裂縫與10周後等待林肯的刺客子彈的象徵性預言聯繫在一起。
修復結果:上圖左邊的第一個圖像是輸入圖像,第二個圖像是掩模,第三個圖像是INPAINT_TELEA的結果,第四個圖像是INPAINT_NS的結果。
讓我們來看一個更復雜的例子。我們已經在一個花園的圖像上草草寫了很多,但是結果仍然非常引人注目。結果如下:
上圖中,左:帶有潦草文字的原始圖像。中:使用INPAINT_TELEA方法修復,右:使用INPAINT_NS。
2.2 代碼
所有代碼見:
https://github.com/luohenyueji/OpenCV-Practical-Exercise
兩種算法修復效果都還不錯,但是都需要事先準備修復模板的掩模mask,也就是inpaintMask 這個參數。例子裏面用鼠標在圖片上劃線,劃線的結果就是mask,而真正應用的時候需要事先設計好這個mask。例子程序中在劃線確定mask後,不同按鍵有不同效果。按t選擇INPAINT_TELEA處理,按n選擇INPAINT_NS處理,按r查看原圖,按ESC退出。
具體代碼如下:
C++:
#include "pch.h"
#include <opencv2/opencv.hpp>
#include <opencv2/photo.hpp>
#include <iostream>
using namespace cv;
using namespace std;
// Declare Mat objects for original image and mask for inpainting
Mat img, inpaintMask;
// Mat object for result output
Mat res;
Point prevPt(-1, -1);
// onMouse function for Mouse Handling
// Used to draw regions required to inpaint
// 調用鼠標事件
static void onMouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))
prevPt = Point(-1, -1);
else if (event == EVENT_LBUTTONDOWN)
prevPt = Point(x, y);
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
Point pt(x, y);
if (prevPt.x < 0)
prevPt = pt;
line(inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0);
line(img, prevPt, pt, Scalar::all(255), 5, 8, 0);
prevPt = pt;
imshow("image", img);
imshow("image: mask", inpaintMask);
}
}
int main()
{
string filename = "./image/flower-garden.jpg";
// Read image in color mode 讀圖
img = imread(filename);
Mat img_mask;
// Return error if image not read properly
if (img.empty())
{
cout << "Failed to load image: " << filename << endl;
return 0;
}
namedWindow("image");
// Create a copy for the original image 複製原圖像
img_mask = img.clone();
// Initialize mask (black image)
inpaintMask = Mat::zeros(img_mask.size(), CV_8U);
// Show the original image
imshow("image", img);
//調用鼠標在圖像上畫圈
setMouseCallback("image", onMouse, NULL);
for (;;)
{
char c = (char)waitKey();
//按t選擇INPAINT_TELEA處理
if (c == 't')
{
// Use Algorithm proposed by Alexendra Telea
inpaint(img, inpaintMask, res, 3, INPAINT_TELEA);
imshow("Inpaint Output using FMM", res);
}
//按n選擇INPAINT_NS處理
if (c == 'n')
{
// Use Algorithm proposed by Bertalmio et. al.
inpaint(img, inpaintMask, res, 3, INPAINT_NS);
imshow("Inpaint Output using NS Technique", res);
}
//按r查看原圖
if (c == 'r')
{
inpaintMask = Scalar::all(0);
img_mask.copyTo(img);
imshow("image", inpaintMask);
}
//按ESC退出
if (c == 27)
{
break;
}
}
return 0;
}
Python:
import numpy as np
import cv2 as cv
# OpenCV Utility Class for Mouse Handling
class Sketcher:
def __init__(self, windowname, dests, colors_func):
self.prev_pt = None
self.windowname = windowname
self.dests = dests
self.colors_func = colors_func
self.dirty = False
self.show()
cv.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
cv.imshow(self.windowname, self.dests[0])
cv.imshow(self.windowname + ": mask", self.dests[1])
# onMouse function for Mouse Handling
def on_mouse(self, event, x, y, flags, param):
pt = (x, y)
if event == cv.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv.EVENT_LBUTTONUP:
self.prev_pt = None
if self.prev_pt and flags & cv.EVENT_FLAG_LBUTTON:
for dst, color in zip(self.dests, self.colors_func()):
cv.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
print("Usage: python inpaint <image_path>")
print("Keys: ")
print("t - inpaint using FMM")
print("n - inpaint using NS technique")
print("r - reset the inpainting mask")
print("ESC - exit")
# Read image in color mode
img = cv.imread("./image/Lincoln.jpg")
# If image is not read properly, return error
if img is None:
return
# Create a copy of original image
img_mask = img.copy()
# Create a black copy of original image
# Acts as a mask
inpaintMask = np.zeros(img.shape[:2], np.uint8)
# Create sketch using OpenCV Utility Class: Sketcher
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv.waitKey()
if ch == 27:
break
if ch == ord('t'):
# Use Algorithm proposed by Alexendra Telea: Fast Marching Method (2004)
# Reference: https://pdfs.semanticscholar.org/622d/5f432e515da69f8f220fb92b17c8426d0427.pdf
res = cv.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv.INPAINT_TELEA)
cv.imshow('Inpaint Output using FMM', res)
if ch == ord('n'):
# Use Algorithm proposed by Bertalmio, Marcelo, Andrea L. Bertozzi, and Guillermo Sapiro: Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting (2001)
res = cv.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv.INPAINT_NS)
cv.imshow('Inpaint Output using NS Technique', res)
if ch == ord('r'):
img_mask[:] = img
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv.destroyAllWindows()
3 參考
https://www.learnopencv.com/image-inpainting-with-opencv-c-python/