이 프로젝트를 시작하기 위해 512x512 크기의 AI 이미지 13,000장을 만들었습니다. 다음 단계는 이미지에 대한 메타데이터를 데이터 테이블에 로드하는 것입니다. 나중에 확대된 이미지의 일부를 대체할 비슷한 색상의 이미지를 선택할 것입니다. 테이블은 다음과 같습니다.
The H,L,S and R,G,B values match the swatch color. The color should match some color in the sample.The H,L,S and R,G,B values match the swatch color. The color should match some color in the sample.The H,L,S and R,G,B values match the swatch color. The color should match some color in the sample.H, L, S 및 R, G, B 값은 견본 색상과 일치합니다. 색상은 샘플의 일부 색상과 일치해야 합니다.
이렇게 하면 파일 이름은 로드되지만, 사진은 아직 로드되지 않습니다.
imageDir = "\\VBOXSVR\images\"; // ~13,000 512x512 AI generated images
files = Files In Directory( imageDir );
nf = N Items( files );
nfolds = 3; // the images are divided up into 3 sets to make sure the same images don't appear too soon
dt = New Table( "lookup",
Add Rows( nf ),
Set Cell Height( 51 ),
New Column( "filename", Character, setvalues( files ), Set Display Width( 356 ) ),
New Column( "wide" ),
New Column( "tall" ),
New Column( "h_" ), // several nearest neighbor techniques tried;
New Column( "l_" ), // the video uses HLS to find similar colors
New Column( "s_" ),
New Column( "r" ),
New Column( "g" ),
New Column( "b" ),
New Column( "sample", Expression, "None", Set Display Width( 85 ) ),
New Column( "swatch", Expression, "None", Set Display Width( 81 ) ),
New Column( "fold123", formula( Mod( Row(), nfolds ) ) ),
New Column( "nUsed", Numeric, "Nominal", <
<SET each="" value=""></SET>
<P>샘플 이미지에서 색상 패치를 얻으려면 RGB 대신 HLS를 사용하여 유사한 색조를 그룹화하고, 색조를 32가지 색상으로 묶습니다. 각 이미지를 로드하고, 축소된 크기의 그림을 샘플 열에 넣습니다(디버깅을 위해서입니다). 다음 코드에서 {hue, lite, sat} = Color To HLS(img << getpixels); 는 512x512 행렬을 hue, sat, lite 변수에 저장합니다. 각 행렬에 대해 요소별 행렬 연산을 수행합니다.</P> <P>HLS의 문제 중 하나는 H(색조, 흔히 색상이라고 생각하는 것) 부분이 흑백을 포함한 회색조에서 무시된다는 것입니다. 저는 회색에 가까운 모든 색상에 H=1.0을 사용하여 함께 비닝되도록 했습니다. 주요 색상 영역이 매우 진한 노란색, 파란색 등인 경우 검은색으로 비닝됩니다.</P> <P>0이었던 H, L, S 값을 1로 묶은 후(nbuckets(32)를 곱하고 결과에 내림 연산을 수행), total이라는 이름의 0으로 채워진 배열을 만드는 명령문이 있습니다. 그 다음에는 ++ 연산자와 복잡한 첨자를 사용하여 해당 배열을 초기화하는 명령문이 있습니다. 나중에 LocMax가 가장 큰 total의 인덱스를 가져올 때, 인덱스를 H, L, S 값으로 역변환하여 해당 배열의 색상을 확인할 수 있습니다. (원래는 32*32*32보다 더 많은 빈이 있을 것이고, 공간 최적화가 더 중요할 것이라고 생각했습니다.)</P> <P>while-keeplooking 루프는 이미지에서 최소 100픽셀을 포함하는 비회색조 색상을 찾으려고 시도합니다. 찾을 수 없는 경우 가장 큰 회색 패치를 사용합니다.</P> <CODE class=" language-jsl">nbuckets = 32; // the HLS values are binned into 32 ranges to find similar colors
nbucketsPlus = nbuckets + 1;
For Each Row(
dt,
path = imageDir || dt:filename;
img = New Image( path );
{dt:wide, dt:tall} = img << size;
{hue, lite, sat} = Color To HLS( img << getpixels );
img << scale( .125 );
dt:sample = img; // for debugging, mostly, keep a small copy in the datatable
If( Any( hue >= 1 ),
Show( Row(), Loc( hue >= 1 ) );
Throw( "bad assumptions that hue<1" );
); // use to flag gray scale colors...
// unsaturated is gray; force the hue to 1. Real hues are 0 to .9999
// similar for very low or high lightness
hue[Loc( sat < 0.4 )] = 1; // gray if unsaturated, may be redundant with one of...
hue[Loc( lite < 0.3 )] = 1; // black if low lightness
hue[Loc( lite > 0.7 )] = 1; // white if high lightness
// binning
hue = Floor( hue * nbuckets );//show(hue) 0..8 inclusive
lite = Floor( lite * nbuckets );//show(lite) 0..8 inclusive
sat = Floor( sat * nbuckets );//show(sat) 0..8 inclusive
total = J( nbucketsPlus * nbucketsPlus * nbucketsPlus, 1, 0 );
// HLS is encoded into the subscript for total[]... decoded further below...
total[((nbucketsPlus ^ 2) * hue + nbucketsPlus * lite + sat + 1)[0]]++;
// the HLS chosen for this picture is the largest patch of a
// binned color that is colorful, or gray if no color seems to stand out...
keeplooking = 1;
firstidx = 0;
While( keeplooking,
idxPlus = Loc Max( total ); // a large bin. Often the first one is not a colorful one.
idx = idxPlus - 1;
If( firstidx == 0,
firstidx = idx // But the first one is the gray level if all are non colorful
);
// arbitrarily, there must be at least 100 colorful pixels of the same binned color,
// or use gray...
If( total[idxPlus] < 100, // no color found, use the original gray and the hue will be 1
Write( "\!nOriginal gray" );
dt:h_ = (Floor( firstidx / (nbucketsPlus ^ 2) ) + .5) / nbuckets;
If( dt:h_ < 1,
Throw( "hue?" )
);
firstidx = Mod( firstidx, (nbucketsPlus ^ 2) );
dt:l_ = (Floor( firstidx / (nbucketsPlus) ) + .5) / nbuckets;
firstidx = Mod( firstidx, (nbucketsPlus) );
dt:s_ = (firstidx + .5) / nbuckets;
keeplooking = 0;//
, //
dt:h_ = Floor( idx / (nbucketsPlus ^ 2) ); // decode HLS uses 0-based idx
If( dt:h_ == nbuckets,
//Write( "\!nignore gray" );
total[idxPlus] = 0;// try another until get a color
, //
dt:h_ = (dt:h_ + .5) / nbuckets; // 0 to 1 recovered from binned index
Write( "\!ngot color" );
idx = Mod( idx, (nbucketsPlus ^ 2) );
dt:l_ = (Floor( idx / (nbucketsPlus) ) + .5) / nbuckets;
idx = Mod( idx, (nbucketsPlus) );
dt:s_ = (idx + .5) / nbuckets;
keeplooking = 0;
);
);
);
If( dt:h_ >= 1,
dt:h_ = 1;
dt:s_ = 0;
);
color = HLS Color( dt:h_, dt:l_, dt:s_ );
{dt:r, dt:g, dt:b} = Color To RGB( color ); // later use the RGB equivalent for kdtable nearest neighbor
dt:swatch = New Image( J( 16, 16, color ) ); // for debugging, need to see a swatch of the chosen color
If( Mod( Row(), 10 ) == 0,
dt << gotorow( Row() );
Wait( 0 );
);
);
For Each Row( dt, Row State( dt, Row() ) = Combine States( Marker State( 12 ), Color State( RGB Color( r, g, b ) ) ) );
KDTable은 다차원 공간에서 가장 가까운 이웃을 찾습니다. 여기서는 4차원을 사용하는데, 1차원이 추가되면 거의 항상 분리된 세 세트의 그림이 생성됩니다.
kdlookup = KDTable( dt[0, {r, g, b, fold123}] ); 다음은 일부 상수와 픽셀 배열, 그리고 시작 이미지입니다.
VideoRows = 1080;
VideoCols = 1920;
VideoSize = Eval List( List( videocols, videorows ) );
FudgeScale = 1.00001; // fix rounding error in <
<SCALE --="" fractional="" scales="" truncate="" big="" and="" small="" are="" both="" actually="" the="" size="" so="" they="" line="" up="">
1e-14, Throw( "xxx", traceback ) ); upscale = N Rows( BigBitmap ) / nrows; ptemp << scale( FudgeScale * upscale ); {ncols, nrows} = ptemp << size; If( ncols != N Cols( BigBitmap ) | nrows != N Rows( BigBitmap ), Throw( Char( ncols ) || " " || Char( nrows ), traceback ) ); // small is initialized to the start/final image which was just upscaled SmlBitmap[0, 0] = -(ptemp << getpixels); // the normally negative jmp colors are kept as positive in big and small ptemp = 0;//release
</SCALE> 비슷한 비트맵을 찾는 작업용 함수입니다. 원래 "작은" 비트맵은 "큰" 비트맵보다 훨씬 작았지만, 이제는 픽셀 정렬을 위해 두 비트맵의 크기가 같습니다. 이 함수는 작은 비트맵의 같은 영역의 평균 색상과 비슷한 색상을 갖도록 선택된 512x512 이미지로 구성된 새로운 큰 비트맵을 만듭니다. 나중에 큰 비트맵과 작은 비트맵을 블렌딩하고 크기를 조정하여 비디오 프레임을 만듭니다.
bigFromSml = Function( {fold}, // makes a new big from the 13K 512x512 choices that aligns with small
{irow, icol, patch, red, green, blue, rows, dists, path, img},
For( irow = 0, irow < N Rows( SmlBitmap ), irow += 512,
Show( irow );
Wait( 0 );
srcrow = Floor( irow ) + 1 :: Floor( irow ) + 512;
For( icol = 0, icol < N Cols( SmlBitmap ), icol += 512,
srccol = Floor( icol ) + 1 :: Floor( icol ) + 512;
patch = SmlBitmap[srcrow, srccol]; // carve out a patch of 512x512 pixels from small
blue = Mod( patch, 256 );
green = Mod( patch -= blue, 256 * 256 );
red = Mod( patch -= green, 256 * 256 * 256 );
// 0..255
blue = Mean( blue );
green = Mean( green ) / 256;
red = Mean( red ) / (256 * 256);
// lookup a bitmap with similar color
nchoices = 5; // pick one of these nearest choices
{rows, dists} = kdlookup << KNearestRows( nchoices, red / 255 || green / 255 || blue / 255 || fold );
If( Mean( dists ) > 1.1,
// if the lookup is returning far-away answers, recycle old pictures
Show( kdlookup << insertrows( 1 :: N Rows( dt ) ) )
);
choice = rows[Random Integer( 1, N Items( rows ) )]; // randomly pick one of the 5 best choices
dt:nUsed[choice] += 10 ^ (4 * fold);// debugging
kdlookup << removeRows( choice );// don't reuse this picture until recycled
path = imageDir || dt:filename[choice];
img = Try(
New Image( path )// during development I might delete a file while this is running
, //
Show( path, choice, exception_msg );
choice = 1;
kdlookup << insertrows( choice );
New Image( imageDir || dt:filename[choice] ); // see if row 1 can keep it going
);
{c, r} = img << size;
img << scale( FudgeScale * 512 / c, FudgeScale * 512 / r ); // a few images need fixups
BigBitmap[srcrow, srccol] = -(img << getpixels); // same location for small and big. store positive jmpcolor
);
)
);
fold = 0;
bigFromSml( fold ); // make the original big before the loop, using the small image 그림 생성 루프를 실행하기 전에 몇 가지 상수를 더 추가하겠습니다. 하나는 몇 가지 숫자를 조정하여 계산한 것입니다. 12.8은 이해하는 데 시간이 좀 걸렸습니다. 확대 속도가 일정하게 보이기 때문입니다. 블렌딩된 크고 작은 비트맵을 확대할수록 동일한 확대/축소 비율을 얻기 위해 가장자리에서 제거해야 하는 픽셀 수가 점점 줄어듭니다. 12.8은 초기 확대/축소 비율과 최종 확대/축소 비율의 비율입니다.
outdir = "\\VBOXSVR\nfsc\deeppic\"; // the video uses a part1 and part2 sequence
// maxrows is the maximum rows that can be removed from the top and bottom without needing to stretch the middle to the VideoRows
maxrows = (N Rows( SmlBitmap ) - VideoRows) / 2;
countmaxrows = 0;
startrows = 48.705; // 48.705 hand picked for 360 frames...6 second transitions
endrows = startrows / (N Rows( SmlBitmap ) / VideoRows);// 12.8 is ratio of start image size to end image size
For( rowReduce = 1, rowReduce <= maxrows, rowReduce += Interpolate( rowReduce, 0, startrows, maxrows, endrows ),
countmaxrows += 1
);
Show( 361, countmaxrows, startrows, endrows );// targeting 180, no, lets use 360 at 60FPS, frames (181-1) (361-1)
마지막으로, 사진을 내보내는 루프입니다. 중첩된 루프입니다. 바깥쪽 루프는 bigFromSmall 함수를 호출하여 안쪽 루프가 360개의 이미지를 추가로 만들 수 있도록 준비합니다. 안쪽 루프는 크고 작은 비트맵에서 점점 더 작은 픽셀 패치를 가져와 크기를 조정하고, 블렌딩한 후, 블렌딩된 이미지를 디스크에 기록합니다.
// 1e12, etc for naming the output files, this loop is not intended to finish; interupt it when there are enough frames
For( iround = 1e12, iround <= 9e12, iround += 1e6,
Show( iround );
Wait( 0.01 );
pictureNumber = 0;
For( rowReduce = 0, rowReduce <= maxrows, rowReduce += Interpolate( rowReduce, 0, startrows, maxrows, endrows ),
If( rowReduce == 0 & iround != 1e12,
Continue()
);// keep first, later it is an expensive duplicate
colReduce = 16 * rowReduce / 9;
// each image is a blend of small and big; the slice from small and big is combined
// and scaled to the video size. the slices get smaller and smaller and need to be scaled
// down less and less until the middle slice needs a scale of 1:1
start = HP Time();
smlPatch = SmlBitmap[Round( rowReduce ) + 1 :: N Rows( SmlBitmap ) - Round( rowReduce ), Round( colReduce ) + 1 :: N Cols( SmlBitmap ) - Round( colReduce )];
smlImg = New Image( -smlPatch );
{ncols, nrows} = smlImg << size;
smlImg << scale( FudgeScale * VideoCols / ncols, FudgeScale * VideoRows / nrows );
If( smlImg << size != VideoSize,
Throw( Char( smlImg << size ), traceback )
);
{smlred, smlgreen, smlblue} = smlImg << getpixels( "rgb" );
bigPatch = BigBitmap[Round( rowReduce ) + 1 :: N Rows( SmlBitmap ) - Round( rowReduce ), Round( colReduce ) + 1 :: N Cols( SmlBitmap ) - Round( colReduce )];
bigImg = New Image( -bigPatch );
{ncols, nrows} = bigImg << size;
bigImg << scale( FudgeScale * VideoCols / ncols, FudgeScale * VideoRows / nrows );
If( bigImg << size != VideoSize,
Throw( Char( bigImg << size ), traceback )
);
{bigred, biggreen, bigblue} = bigImg << getpixels( "rgb" );
stop = HP Time();
// blend and save the scaled slices
alpha = 1 - (pictureNumber / countmaxrows);
beta = 1 - alpha;
combine = New Image( "rgb", {bigred * beta + smlred * alpha, biggreen * beta + smlgreen * alpha, bigblue * beta + smlblue * alpha} );
combine << savepicture( outdir || Char( iround + (pictureNumber += 1) ) || ".jpg", "jpg" );
Write( "\!n" || Char( (stop - start) / 1e6, 6 ) || " " || Char( iround - 1e12 ) || " " || Char( pictureNumber ) );
Wait( 0.01 );
);
// combine becomes the new smlBitmap
combine << scale( FudgeScale * upscale );
{ncols, nrows} = combine << size;
If( ncols != N Cols( BigBitmap ) | nrows != N Rows( BigBitmap ),
Throw( Char( ncols ) || " " || Char( nrows ), traceback )
);
SmlBitmap[0, 0] = -(combine << getpixels);
combine = 0;//release
// and make a new big bitmap from small
fold = Mod( fold + 1, nfolds );
bigFromSml( fold );
); 사진을 만든 후에는 Blender 비선형 편집기에서 사진을 불러와서 비디오를 만들 수 있습니다. 위의 "큰" 비트맵이 이미 너무 커서 공간 해상도를 높이는 대신 60FPS로 설정했습니다.
Blender is open source; I use it on Linux. I think Win and Mac versions are available. It has a long learning curve.Blender is open source; I use it on Linux. I think Win and Mac versions are available. It has a long learning curve.Blender is open source; I use it on Linux. I think Win and Mac versions are available. It has a long learning curve.블렌더는 오픈 소스라서 리눅스에서 사용하고 있습니다. 윈도우와 맥 버전도 있는 것 같습니다. 배우는 데 시간이 꽤 걸립니다.
다 끝났어요. 전환이 마음에 들어요. 색상 선택과 블렌딩이 잘 어우러지네요.
사무실로 돌아왔다면 시작하기 전에 볼륨을 조금 낮추는 게 좋을 겁니다.