LoginSignup
3
4

More than 5 years have passed since last update.

画像を4対3から16対9に変換する

Last updated at Posted at 2017-03-14

4:3の画像を16:9に変換したいときもあるかと思います。
たくさん画像があるときに一つ一つ手で処理するのは大変なので、良い感じに変換できないかやってみようと思います。
10年以上前に、画像のノイズ除去プログラムなどスクラッチから書いた記憶があるのですが、今回はImageMagickというOSSを利用してみたいと思います。

今回、編集する画像はこちらです。

■4:3
- 640x480画像(以降、 img43.jpeg とする)

640480

  • モノクロBMP版(qiitaってBMPアプロードできないんだ... img43BMP.bmp とする)

スクショbmp.png

目次

ImageMagickを使って4:3から16:9に変換する

まずはクラウド関係なしに、画像変換をしてみます。
ImageMagickは最近でもメンテが行われているようなので、こちらを使いたいと思います。

インストールする

Google Cloud Platformの、Compute Engine上に構築してみます。
OSはCentOS7です。

ImageMagickをインストール。
PHPで色々とプログラミングする予定なので、Apache2.4とPHP7.1を同時進行的にインストールします。
また、PHPからImageMagickを使うのに楽になる ImagickというPHP拡張モジュールもインストールします。
DB操作するのに楽になるPDOみたいなものがあるわけですね。

インストールスクリプト

//Apacheインストール
sudo yum install -y httpd httpd-devel

//PHP7.1とImageMagickをインストール
sudo yum install -y epel-release
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

sudo yum install --enablerepo=remi -y php71-php php71-php-devel php71-php-mbstring php71-php-pdo php71-php-pecl php71-php-pdo php71-php-pear

sudo yum install ImageMagick

sudo yum install php71-php-pecl-imagick php71-php-pecl-imagick-devel

//Apacheを起動しておく
sudo systemctl start httpd

phpinfoで確認してみます。check.phpとしてファイルを作成します。

check.php
<?php

  phpinfo();

?>

ブラウザ経由でファイルにアクセスして、使えるようになっているか確認。
phpinfo.png

画像を加工してみる

img43, img43BMPの両方をサーバに保存しておきます。
場所は /var/www/html配下に置きます。

例えばwget

sudo yum install wget

wget https://qiita-image-store.s3.amazonaws.com/0/147747/73f4febb-0061-2aa7-1bea-26df9ac6fed5.jpeg

cp 73f4febb-0061-2aa7-1bea-26df9ac6fed5.jpeg img43.jpeg

サーバ内部でファイル名をimg43.jpegにしたものを確保しました。
早速、画像を加工してみます。
リファレンスを見ながら、白と黒を反転させるコードを書いてみます。
http://php.net/manual/ja/book.imagick.php

negate.php

<?php

header('Content-type: image/jpeg');

$image = new Imagick('img43.jpeg');

$image->negateImage(true);

echo $image;

?>
  • ブラウザからアクセスしてみます。
    negate.png
  • 元画像はこちら。反転できてますね。
    640480

自力でねじり込んでみる

To Be Continued...

単純に4:3から16:9に変換する

4:3の640x480を、16:9の1280x720に変換します。

Imagickで選択できる変換処理は以下4つ。
画質を変えずに良しなに処理したいだけなのに違いがわからない。速度よりも「機能」で選びたいのですが。。

ResizeImage

http://php.net/manual/ja/imagick.resizeimage.php

色々な"フィルタ"をパラメータとして設定することが出来、画像サイズを変更しつつも加工ができるらしい。

AdaptiveResizeImage

http://php.net/manual/ja/imagick.adaptiveresizeimage.php

フィルタの設定なしに、良しなに画像サイズを変更してくれる模様

adaptiveRI.php
<?php

header('Content-type: image/jpeg');

$image = new Imagick('img43.jpeg');

$image->adaptiveResizeImage(1280,720);

echo $image;

?>
  • なんか「かすれ」た
    kasure.png

比率を変えるだけでなく、倍くらいの大きさにしてしまったのだがソレが原因だろうか。。
良しなにやってくれるのはありがたいが、「かすれ」てしまうのなら懸念材料ですね。

ScaleImage

http://php.net/manual/ja/imagick.scaleimage.php

scaleImage.php
<?php

header('Content-type: image/jpeg');

$pict = new Imagick('img43.jpeg');

$pict->scaleImage(1280,720);

echo $pict;

?>
  • かすれない。

kasurenai.png

ThumbnailImage

http://php.net/manual/ja/imagick.thumbnailimage.php

結局どれを使えば良いのか

面白い記述をstackoverflowで見つけました。
http://stackoverflow.com/questions/5285345/imagickresizeimage-vs-imagickscaleimage

According to my finding, when you want to scale down an image, use resizeImage.

In second case, when you have to scale up and image, then resizeImage will definetely have graphical error like while marks and lines and other stuff. In that case, scaleImage will come to rescue.

画像を大きくするリサイズの時はscaleImageで、
画像を小さくするリサイズの時はresizeImageを使え。。。とある。

得意不得意があるということですかね。
AdaptiveResizeImageが拡大に向いていないのなら、「かすれ」てしまったことも納得出来ます。

現時点では決めきれないので、取り敢えずはResizeImageとAdaptiveResizeImageを候補に残したまま進めていきます。

変換後の隙間の埋め方を考える

4:3から16:9に変換するにあたり、2種類のはめ込み方があります。

そもそも、4:3と16:9は異なるアスペクト比であり、サイズをあわせようとしても差異が発生します。

例えば、640x480サイズであるimg43.jpegを、16:9である854x480に載せてみます。
下図のように縦幅は合っているのですが、横幅が合わないために黒い隙間が出現してしまいます。これが「ピラーボックス」形式です。
pillar.png

iPhoneのsafariから、youtubeの古い動画を再生してみると、以下のようなピラーボックス形式で表示されます。右上のzoomアイコンをタップしてみると。。。
pillar.png

下図のようにzoomされ、横幅にフィットした状態で動画が再生されます。
zoomFit.png

16:9の横幅にフィットした状態になっているわけですが、そもそも差異があるのにおかしいですよね。
ザックリと図示すると、以下のように上部と下部がカットされているわけです。
zoomCut.png


ここで、U-NEXTやHuluのページを見てみましょう。
http://video.unext.jp/genre/anime
http://www.hulu.jp/

ピラーボックス状態のサムネイル画像など一切なく、ポスターサイズを16:9に当てはめたり、うまい具合にzoomをしている画像ばかりです。
よくわからない隙間があるよりも、こちらの方がイケてますよね。

zoom形式で隙間を埋めようと思います。

アス比を変えずに横幅をあわせる

目標とする解像度を定めます。取り敢えずyoutubeから。

推奨される解像度とアスペクト比
2160p: 3840x2160
1440p: 2560x1440
1080p: 1920x1080
720p: 1280x720
480p: 854x480

処理順序を考えます。
#1.横幅を1280になるように拡大する
#2.縦幅を720になるように切り取り

以下の関数を利用します。
- AdaptiveResizeImage
- pingImageBlob
- cropImage

数字ベタ打ちですが、こんな感じのプログラムを書いてみました。

zoom.php
<?php

header('Content-type: image/jpeg'); 

$pict = new Imagick('img43.jpeg');

$pict->adaptiveResizeImage(1280,0);

$im = new Imagick();
$im->pingImageBlob($pict);

$fy = $im->getImageHeight();
$fy = ($fy-720)/2;

$pict->cropImage(1280,720 , 0, $fy);

echo $pict;

?>

  • こんな感じの画像が出来ました。上で「ザックリと図示」したものと似た感じになりました。
    zoom0.jpeg

ベタ打ちにならぬよう、以下の解像度に合わせてうまい具合になるようにプログラムを書き直してみます。
そもそも4:3画像かどうかなど、チェックした方が良さそうですが。

2160p: 3840x2160
1440p: 2560x1440
1080p: 1920x1080
720p: 1280x720
480p: 854x480

zoom2.php
<?php

header('Content-type: image/jpeg');

$pict = new Imagick('img43.jpeg');

$im = new Imagick();
$im->pingImageBlob($pict);

$tx = 0;
if($im->getImageWidth() < 1280){
  $tx = 854;
  $ty = 480;
}else if($im->getImageWidth()<1920){
  $tx = 1280;
  $ty = 720;
}else if($im->getImageWidth()<2560){
  $tx = 1920;
  $ty = 1080;
}else if($im->getImageWidth()<3840){
  $tx = 2560;
  $ty = 1440;
}else{
  $tx = 3840;
  $ty = 2160;
}

$pict->adaptiveResizeImage($tx,0);

$rim = new Imagick();
$rim->pingImageBlob($pict);

$fy = $rim->getImageHeight();
$fy = ($fy-$ty)/2;

$pict->cropImage($tx, $ty , 0, $fy);

echo $pict;

?>

何はともあれ、4:3画像を16:9画像にzoomスタイルで変換できるようになりました。

Zoomの位置を調整する

ここまではほぼ意味のない画像を取り扱ってきたので、ど真ん中にフォーカスしてzoomするようにしていました。
希望としては、画像の価値のある部分にフォーカスして切り取りをしたいのですが、「価値があるか・ないか」を識別するプログラムを構築するのは些か難しいところがあります。
そこで人工知能を利用してみたいと思います。

個人的にはIBMのAlchemy APIを利用したことがあったのですが、どうやら無くなるようですね。
https://www.ibm.com/blogs/bluemix/2017/03/bye-bye-alchemyapi/

取り敢えず、社内でもモチモチ使っているMicrosoft Azureの人工知能を使ってみようと思います。

Face APIを利用してzoomする

以下の4:3画像を加工してみます。(from ぱくたそ(www.pakutaso.com))
zoom0.jpeg

zoom2.phpで読み込むファイルを単純にこちらにしてみると、以下のような画像に処理される。
スクリーンショット 2017-03-09 16.36.57.png

人物が見切れてしまっている。期待動作としては、もっと上の方にzoomして欲しい。

再度、処理順序を考えます。
#1.横幅を1280になるように拡大する
#2.縦幅を720になるように切り取り。ただし、人の顔が入るように。

Face APiのサンプルはこちらですかね。
https://www.microsoft.com/cognitive-services/en-us/face-api/documentation/Face-API-How-to-Topics/HowtoDetectFacesinImage

APi自体のリファレンスはこちらですかね。Azure API ManagementのようにPHPなどのサンプルプログラムもあります。
https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395239

色々なドメインに資料が散らばっており、面倒な感じになっておりやす。

とにかく叩いてみる

APiリファレンスのページにPHPのサンプルプログラムがあるので、それをベースに書き換えます。

Request2.phpが必要らしいので、yumで入れておきます。

sudo yum install php-pear-HTTP-Request2
face.php
<?php
// This sample uses the Apache HTTP client from HTTP Components (http://hc.apache.org/httpcomponents-client-ga/)
require_once 'HTTP/Request2.php';

$request = new Http_Request2('https://westus.api.cognitive.microsoft.com/face/v1.0/detect');
$url = $request->getUrl();

$headers = array(
    // Request headers
    'Content-Type' => 'application/json',
    'Ocp-Apim-Subscription-Key' => 'Azureのポータルから取得するアクセスKEYを入れる',
);

$request->setHeader($headers);

$parameters = array(
    // Request parameters
    'returnFaceId' => 'true',
    'returnFaceLandmarks' => 'false',
    'returnFaceAttributes' => 'age,gender',
);

$url->setQueryVariables($parameters);

$request->setMethod(HTTP_Request2::METHOD_POST);

// Request body
$request->setBody(json_encode(array("url"=>"画像のURLを入れる.jpeg")));

try
{
    $response = $request->send();
    echo $response->getBody();
}
catch (HttpException $ex)
{
    echo $ex;
}

?>

echoに値が入っている。簡単。。。

[
 {"faceId":"数字が入っている",
  "faceRectangle":{"top":46,"left":230,"width":46,"height":46},
  "faceAttributes":{"gender":"male","age":39.1}
 }
]
  • この数値をもとに、画像に四角を描いてみました。大丈夫そうですね。
    スクリーンショット 2017-03-10 14.48.01.png

  • 改めて処理を考える。
    画像内の価値を分析して、その価値が最大限になるようなフォーカスをあてるべきとはわかっているものの、取り敢えず顔が入るようにやってみます。

#1.横幅を1280になるように拡大する
#2.縦幅を720になるように切り取り。ただし、人の顔が入るように。

この工程#2についてですが、人の顔が入っていることを条件にするとギリギリまでしか入らない気がするので、あくまで人の顔が中心になるように考えてみます。

#2-1.人の顔の左上の位置(α)を確保
#2-2.αを中心に上下に720px幅を確保。上座標をtopα、下座標をbtmαとする
#2-3-1.元画像より上にtopαが突き抜ける場合は、元画像の上端を新画像上辺とする
#2-3-2.元画像より下にbtmαが突き抜ける場合は、元画像の下端を新画像下辺とする

イメージとしては下図のような感じです。
スクリーンショット 2017-03-10 14.48.01.png

resized16
<?php

$pict = new Imagick('person.jpeg');

//16:9リサイズ
$nx = 0;
$ny = 0;

$im = new Imagick();
$im->pingImageBlob($pict);

$ori_width = $im->getImageWidth();
$ori_height = $im->getImageHeight();

if($ori_width < 1280){
    $nx = 854;
    $ny = 480;
}else if($ori_width < 1920){
    $nx = 1280;
    $ny = 720;
}else if($ori_width < 2560){
    $nx = 1920;
    $ny = 1080;
}else if($ori_width < 3840){
    $nx = 2560;
    $ny = 1440;
}else{
    $nx = 3840;
    $ny = 2160;
}

$pict->adaptiveResizeImage($nx,0);

file_put_contents('rperson.jpeg', $pict);

//16:9リサイズ END

//顔の左上座標取得
$f_top = 0;
$f_left = 0;

// This sample uses the Apache HTTP client from HTTP Components (http://hc.apache.org/httpcomponents-client-ga/)
require_once 'HTTP/Request2.php';

$request = new Http_Request2('https://westus.api.cognitive.microsoft.com/face/v1.0/detect');
$url = $request->getUrl();

$headers = array(
    // Request headers
    'Content-Type' => 'application/json',
    'Ocp-Apim-Subscription-Key' => '自分のキー',
);

$request->setHeader($headers);

$parameters = array(
    // Request parameters
    'returnFaceId' => 'true',
    'returnFaceLandmarks' => 'false',
    'returnFaceAttributes' => 'age,gender',
);

$url->setQueryVariables($parameters);

$request->setMethod(HTTP_Request2::METHOD_POST);

// Request body
$request->setBody(json_encode(array("url"=>"http://自分のURL/rperson.jpeg")));

try
{
    $response = $request->send();
    $arr = json_decode($response->getBody(),true);
    $f_top = $arr[0]['faceRectangle']['top'];
    $f_left = $arr[0]['faceRectangle']['left'];

}
catch (HttpException $ex)
{
//    echo $ex;
}

//顔の左上座標取得 END

//顔の左上の位置(α)を中心に、縦幅を確保。できなければ調整
$new_img_top = 0;

$height_seeker = $ny/2;

$topa = $f_top - $height_seeker;
$btma = $f_top + $height_seeker;

$pict2 = new Imagick('rperson.jpeg');

$rim = new Imagick();
$rim->pingImageBlob($pict2);

$resized_width = $rim->getImageWidth();
$resized_height = $rim->getImageHeight();

$new_img_top = $topa;

if($topa < 0 ){
    //切り取りベース画像の上端を突き抜けている場合、上端を上辺とする
    $new_img_top = 0;

}else if($btma > $resized_height){
    //切り取りベース画像の下端を突き抜けた場合、下端を下辺とする
    $new_img_top = $resized_height- $ny;
}

$resized = new Imagick('rperson.jpeg');

$resized->cropImage($nx,$ny,0,$new_img_top);

file_put_contents('16by9.jpeg', $resized);

?>
  • 結果
    大丈夫そう。
    スクリーンショット 2017-03-14 23.37.38.png

  

今度は、顔が画像の下の方にあるバージョン(下端が下辺になるはず)

スクリーンショット 2017-03-10 14.48.01.png

これがこう

btm.png

この画像だともっとズームしても良さそうだが、、

とりあえず、16:9の画像に変換するプログラムを書いてみました。
次回はサムネイル自動作成マシーンと化したAzureをお見せしたいと思います。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4