[OpenCV實戰]34 使用OpenCV進行圖像修復

目錄

1 什麼是圖像修復

1.1 INPAINT_NS : Navier-Stokes based Inpainting

1.2 INPAINT_TELEA : Fast Marching Method based

1.3 方法比較與函數實現

2 結果與代碼

2.1 結果

2.2 代碼

 3 參考


本文將描述一類稱爲圖像修復的區域填充算法。想象一下找一張舊的家庭照片。你掃描它,它看起來很棒,除了一些劃痕。當然,你可以在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信號,並應用信號處理理論來解決計算機視覺問題。另一方面,數學家可以將圖像看作連通圖並使用圖論解決計算機視覺問題。因此,爲流體動力學開發的理論也可以用於計算機視覺,這並不奇怪。在下圖中,我們的目標是填充暗區並獲得一個看起來像右邊的圖像。

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

我們如何填補這個黑色區域?我們想要的一個約束是邊緣進入點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 );
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

Python:

dst = cv2.inpaint(src, inpaintMask, inpaintRadius, flags)
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

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周後等待林肯的刺客子彈的象徵性預言聯繫在一起。

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

修復結果:上圖左邊的第一個圖像是輸入圖像,第二個圖像是掩模,第三個圖像是INPAINT_TELEA的結果,第四個圖像是INPAINT_NS的結果。

讓我們來看一個更復雜的例子。我們已經在一個花園的圖像上草草寫了很多,但是結果仍然非常引人注目。結果如下:

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

上圖中,左:帶有潦草文字的原始圖像。中:使用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;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章