PSS SDKで画像処理サンプルを作成(魚眼カメラ風ひずみ)
PSS SDK
先日,ソニーからPlayStaion Vitaなどでソフト開発を行えるツールキット "PlayStation Suite SDK" が公開されました.
PlayStation®Suite
今回はそれを使ってサンプルソフトを作ってみたので,それについて記していきます.
C#でコーディング
C#はまったく書いたことが無かったのですが,この機にと思って書き始めてみたところ,Javaとあまり変わらず(むしろこっちのほうが楽かも)書くことができました.
配列の書き方など,C#ならではの作法はありますので,以下で調べながら書きました.
C# プログラミング ガイド
魚眼カメラ風のレンズひずみソフト
今回は,画像処理のソフトを作りました.
画像を読み込んで(ここはまだGUI実装しておらず,ベタ書きですが),魚眼カメラ風にひずませて表示するものです.
たとえばこんな画像を入力すると,
こんなふうに描画してくれます.
レンズひずみのモデルは以下を参考にしました.
レンズ歪み
ちなみに,いじった方はわかると思いますが,上記はチュートリアルのsample02-01を改変して作りました.
そのなかで,
- テクスチャ座標系と頂点座標系が線形変換でないときは,行ごとにわけて描画しないと正しくかかれない
- シェーダの呼び出しは1フレームに複数回できる(当たり前っぽいですが)
など,いろいろ腹落ちしたのでだいぶ勉強になりました.
次はGUIやHTTP通信部分など,実用の際に避けては通れない部分を見ていきたいと思います.
ソースコード
最後に,以下にcsファイルを記載します.
githubでも公開しています.
glass5er/PSS_fisheye · GitHub
/* SCE CONFIDENTIAL * PlayStation(R)Suite SDK 0.98.2 * Copyright (C) 2012 Sony Computer Entertainment Inc. * All Rights Reserved. * * Fisheye Distortion Sample Program * Author : Kentaro Doba <glass5er@gmail.com> * Date : 2012/04/22 */ using System; using Sce.Pss.Core; using Sce.Pss.Core.Environment; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Imaging; using Sce.Pss.Core.Input; namespace Sample { public class AppMain { // default graphic context // static protected GraphicsContext graphics; // shader // static ShaderProgram shaderProgram; // texture // static Texture2D texture; // polygon vertex coordinates (to be determined) // const int sliceNumV = 24;//5*2 + 1; const int sliceNumH = 24;//5*2 + 1; const int sliceNumTotal = sliceNumH * sliceNumV; // const int sliceNumDraw = 2 * sliceNumH; // vertex coordinates (TBD) // static float[] vertices=new float[sliceNumTotal * 3]; // texture coordinates (TBD) // static float[] texcoords = new float[sliceNumTotal * 2]; // texture screen colors (TBD) // static float[] colors = new float[sliceNumTotal * 4]; // distortion coefs // static float[] ud_coefs = { 3.5f, 3.5f, 0.0f, 0.0f }; const int indexSize = sliceNumDraw; static ushort[][] indices; static int sign = -1; static float objRatio = 5.0f; static float objRelativeWidth, objRelativeHeight; static int screenWidth, screenHeight; static VertexBuffer vertexBuffer; // Width of texture. static float Width; // Height of texture. static float Height; static Matrix4 unitScreenMatrix; //--------// // main // //--------// public static void Main (string[] args) { Initialize (); // PSS MainLoop // while (true) { SystemEvents.CheckEvents (); Update (); Render (); } } public static void Initialize () { // define screen // graphics = new GraphicsContext(); ImageRect rectScreen = graphics.Screen.Rectangle; screenWidth = rectScreen.Width; screenHeight = rectScreen.Height; // read image for texture // //texture = new Texture2D("/Application/resources/Player.png", false); texture = new Texture2D("/Application/resources/cat.png", false); // compile shader // shaderProgram = new ShaderProgram("/Application/shaders/Sprite.cgx"); shaderProgram.SetUniformBinding(0, "u_WorldMatrix"); // get texture image size // Width = texture.Width; Height = texture.Height; // calc vertex coordinates // for(int i = 0; i<sliceNumTotal * 4; i++) { colors[i] = 1.0f; } calcVertices(); // texture mapping order // indices = new ushort[sliceNumV-1][]; for(int iy=0; iy<sliceNumV-1; iy++) { int cur_idx = 0; int ref_idx = iy * sliceNumH; indices[iy] = new ushort[sliceNumDraw]; for(int ix=0; ix<sliceNumH; ix++) { indices[iy][cur_idx++] = (ushort)ref_idx; ref_idx += sliceNumH; indices[iy][cur_idx++] = (ushort)ref_idx; ref_idx += 1 - sliceNumH; } } // construct vertex shader // // vertex_num(total), vertex_num(used with index), vertex_format, texture_format, color_format // vertexBuffer = new VertexBuffer(sliceNumTotal, indexSize, VertexFormat.Float3, VertexFormat.Float2, VertexFormat.Float4); // set {vertex, texture, color} // vertexBuffer.SetVertices(0, vertices); vertexBuffer.SetVertices(1, texcoords); vertexBuffer.SetVertices(2, colors); // set shader // graphics.SetVertexBuffer(0, vertexBuffer); objRelativeWidth = Width*objRatio/screenWidth; objRelativeHeight = Height*objRatio/screenHeight; unitScreenMatrix = new Matrix4( objRelativeWidth, 0.0f, 0.0f, 0.0f, 0.0f, -objRelativeHeight, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -objRelativeWidth/2.0f, objRelativeHeight/2.0f, 0.0f, 1.0f ); } public static void Update () { // update size ratio // if(objRatio > 5.0f) { sign = -1; } if(objRatio < 4.0f) { sign = 1; } objRatio += (sign * 0.015625f); objRelativeWidth = Width*objRatio/screenWidth; objRelativeHeight = Height*objRatio/screenHeight; // write ratio to matrix // unitScreenMatrix.M11 = objRelativeWidth; unitScreenMatrix.M22 = -objRelativeHeight; unitScreenMatrix.M41 = -objRelativeWidth / 2.0f; unitScreenMatrix.M42 = objRelativeHeight / 2.0f; } public static void Render () { // init screen // graphics.Clear(); // link shader // graphics.SetShaderProgram(shaderProgram); graphics.SetTexture(0, texture); shaderProgram.SetUniformValue(0, ref unitScreenMatrix); // draw every row // for(int iy = 0; iy<sliceNumV/2; iy++) { vertexBuffer.SetIndices(indices[iy]); graphics.DrawArrays(DrawMode.TriangleStrip, 0, indexSize); } for(int iy = sliceNumV-2; iy>=sliceNumV/2; iy--) { vertexBuffer.SetIndices(indices[iy]); graphics.DrawArrays(DrawMode.TriangleStrip, 0, indexSize); } // finish drawing // graphics.SwapBuffers(); } public static void calcVertices () { // calc vertex coordinates // for(int iy = 0; iy<sliceNumV; iy++) { for(int ix = 0; ix<sliceNumH; ix++) { // texture index // int idx_t = (iy * sliceNumH + ix) * 2; // vertex index // int idx_v = (iy * sliceNumH + ix) * 3; // texture coordinates // float tmp_tx = (float)ix/(float)(sliceNumH - 1); float tmp_ty = (float)iy/(float)(sliceNumV - 1); texcoords[idx_t + 0] = tmp_tx; // x texcoords[idx_t + 1] = tmp_ty; // y // vertex coordinates // float tmp_vx = (float)ix/(float)(sliceNumH - 1); float tmp_vy = (float)iy/(float)(sliceNumV - 1); undistort_wrap(ref tmp_vx, ref tmp_vy); vertices[idx_v ] = tmp_vx; // x vertices[idx_v + 1] = tmp_vy; // y vertices[idx_v + 2] = 0.0f; // z } } } public static void distort_wrap(ref float rx, ref float ry) { // translate // rx -= 0.5f; ry -= 0.5f; // distort_core // distort(ref rx, ref ry); // translate // rx += 0.5f; ry += 0.5f; } public static void undistort_wrap(ref float rx, ref float ry) { // translate // rx -= 0.5f; ry -= 0.5f; // undistort_core // undistort(ref rx, ref ry); // translate // rx += 0.5f; ry += 0.5f; } public static void distort(ref float rx, ref float ry) { float xy = rx * ry; float x2 = rx * rx; float y2 = ry * ry; float r2 = x2 + y2; // radial distortion // float k_rad = 1.0f + ud_coefs[0] * r2 + ud_coefs[1] * r2*r2; // circumferential direction // float dx = 2.0f * ud_coefs[2] * xy + ud_coefs[3] * (r2+2*x2); float dy = ud_coefs[2] * (r2 + 2*y2) + 2 * ud_coefs[3] * xy; // distort // rx = (rx-dx) / k_rad; ry = (ry-dy) / k_rad; } public static void undistort(ref float rx, ref float ry) { float xy = rx * ry; float x2 = rx * rx; float y2 = ry * ry; float r2 = x2 + y2; // radial distortion // float k_rad = 1.0f + ud_coefs[0] * r2 + ud_coefs[1] * r2*r2; // circumferential direction // float dx = 2.0f * ud_coefs[2] * xy + ud_coefs[3] * (r2+2*x2); float dy = ud_coefs[2] * (r2 + 2*y2) + 2 * ud_coefs[3] * xy; // undistort // rx = rx * k_rad + dx; ry = ry * k_rad + dy; } } }