GraphCut Texture
Intro
学长之前给我发了一个文章,解决的问题是如何拼接图像,我当时看他做了一些纹理合成的工作,我自身也比较感兴趣,所以去看了一下这个方法。这个方法比较老,应该是十多年前的算法,但现在看起来仍然很惊艳,输入纹理图像的一部分,对纹理图像旋转缩放等生成另一张图片,随机摆放两张图,两张图有重叠部分,重叠部分用mincut算法得到割边,将左右部分划分开来,同时减少边缘的不平滑。该方法不仅可以实现纹理生成,还能实现不同图片拼接、视频合成等等神奇操作。
算法的一些效果图如下:
Method
算法主要分两步:
- 挑选原图中的局部图像,放置到输出图上
- 通过GraphCut,寻找局部图像块和原始输出图像中已有像素间的缝隙,确定输出图像中哪些属于左边的图像,哪些属于右边的图像。
如图seam是两图的连接缝,通过GraphCut计算得到的。
GraphCut
该算法的核心就是如何计算两重叠图像的连接缝,我们将原图的两个patch随机重叠摆放,要使得输出的连接图像之间没有明显的边界,要解决的问题大致可以表述为:
如上图所示,我们需要在重合的两个patch中找到一条路径,使得这条路径的左边都替换为patch A的像素,右边都替换为patch B的像素。而将其看成图,即可表述为右边的描述。
而这种问题其实对应的是图的最小割问题,即给定带权无向图,将图分割成两个互不相交的集合,并且使得两个集合相连的所有边的权重之和最小。
无向图的最小割算法有Stoer-Wagner算法:
- 先利用类似最大生成树的方法求得最后两个生成的节点和只剩两个节点时的权值,记录下来。
- 每次将最后两个节点合并,将合并后的节点更新权值和连接,再执行1的算法,直到只剩1个节点。
应用到本问题,只需要解决节点之间权值的定义即可。
定义任意两相邻像素s和t之间的权值为:
\[ M(s,t,A,B) = ||A(s) - B(s)||+||A(t) - B(t)|| \]
其中A和B分别表示两个patch图像的重合部分。
Coding
跑起来比较慢。
import cv2 as cv
import numpy as np
import copy
import random
class MinCut(object):
def __init__(self,img1,img2):
h,w = img1.shape
#self.num_points = 8
self.num_points = h*w
print(self.num_points)
self.v = np.arange(0,self.num_points)
self.g = np.zeros((self.num_points,self.num_points)) + np.inf
self.w = np.zeros((self.num_points,))
self.r = 0
self.squ = np.zeros((self.num_points,),dtype = np.int)
self.index = 0
self.min_cut = np.inf
for i in range(h):
for j in range(w):
# 上
if i-1 >=0:
self.g[i*w+j,(i-1)*w+j] = np.abs(img1[i-1,j] - img2[i-1,j]) + np.abs(img1[i,j] - img2[i,j])
# 下
if i+1 < h:
self.g[i*w+j,(i+1)*w+j] = np.abs(img1[i+1,j] - img2[i+1,j]) + np.abs(img1[i,j] - img2[i,j])
# 左
if j-1 >=0:
self.g[i*w+j,i*w+j-1] = np.abs(img1[i,j-1] - img2[i,j-1]) + np.abs(img1[i,j] - img2[i,j])
# 右
if j+1 < w:
self.g[i*w+j,i*w+j+1] = np.abs(img1[i,j+1] - img2[i,j+1]) + np.abs(img1[i,j] - img2[i,j])
'''
self.g = np.array([
[0,3,3,3,0,0,0,0],
[3,0,3,3,0,1,0,1],
[3,3,0,3,0,0,0,0],
[3,3,3,0,0,0,0,0],
[0,0,0,0,0,3,2,2],
[0,1,0,0,3,0,2,2],
[0,0,0,0,2,2,0,0],
[0,1,0,0,2,2,0,0]
])
'''
def __call__(self):
n = copy.copy(self.num_points)
result = {"a":[],"b":[]}
while(n >1):
pre = 0
self.w = np.zeros((self.num_points,))
visited = np.zeros((self.num_points,))
for i in range(1,n):
k = -1
for j in range(1,n):
if not visited[self.v[j]]:
self.w[self.v[j]] += self.g[self.v[pre],self.v[j]]
if k == -1 or self.w[self.v[k]] < self.w[self.v[j]]:
k=j
visited[self.v[k]] = 1
if i == n-1:
s = self.v[pre]
t = self.v[k]
self.squ[self.r]=t
self.r += 1
print(t,"-->",s)
if self.w[t] < self.min_cut:
self.min_cut = self.w[t]
self.index = self.r
for j in range(n):
self.g[s,self.v[j]] += self.g[self.v[j],t]
self.g[self.v[j],s] += self.g[self.v[j],t]
self.v[k] = self.v[n-1]
n-=1
pre = k
for i in range(self.num_points):
if i >= self.index:
result["b"].append(i)
else:
result["a"].append(i)
return result["a"],result["b"]
if __name__ == "__main__":
img1 = cv.imread("/home/xueaoru/图片/aaa.png",0)
img2 = cv.imread("/home/xueaoru/图片/bbb.png",0)
#h,w = img.shape
img1 = cv.resize(img1,(50,50))
img2 = cv.resize(img2,(50,50))
#img = img / 255.
img_1 = np.array(img1,np.float)
img_2 = np.array(img2,dtype = np.float)
gcut = Graph(img_1,img_2)
left,right = gcut.mincut()
merged = np.zeros((50,50),dtype = np.uint8)
for i in range(50):
for j in range(50):
if i*50 + j in left:
merged[i,j] = img1[i,j]
else:
merged[i,j] = img2[i,j]
cv.imshow("img1",img1)
cv.imshow("img2",img2)
cv.imshow("merged",merged)
Accounting for Old Seams
算法实际实现过程中还将之前的割缝保存了下来,下一次再求割缝的时候利用之前的割缝,并且规定了一些权值定义的规则,不再赘述。
后面没仔细看了,还有一部分是视频合成。