
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、完全に個人的な趣味なんですけど、私はマップでいろいろなところを「地図旅行」するのが好きです。
特に国土地理院が提供してくれている過去の航空写真(1960年代とか)の「時間旅行&地図旅行」が最高だったりします。
しかし、それにはひとつ問題があって、
画質が荒い
んですよね…(もちろん、当時としてはすごいのですが)
そして、「うーん、もうちょっと拡大して見てみたい」と何度も思ってたら1つアイデアが浮かんできました。
地図画像を高画質化すればいいじゃないか
と。
というのも、やはり科学はすごいですね。
なんと、Real-ESRGAN という技術を使うとPython
で画像を高画質化することができるんですね。(しかもこのパッケージも2年ぐらい前のものです)
そこで!
今回は「Laravel + Fast API + Real-ESRGAN」で「クリックするだけで画像を高画質化する」機能をつくってみたいと思います。
ぜひ何かの参考になりましたら嬉しいです。
※ ちなみにFast API
は私が使ってみたかったからです。PHP
からでもコマンド実行できるならいけると思います。
「夏のサイクリング用に
取り付け式の空調ファン
を購入今年も行くぞ
」
開発環境: Laravel 10.x、Fast API
目次 [非表示]
全体の構成
今回は、Laravel
(PHP
)だけでなく、Python
も使うので全体像を先にご紹介します。
Laravel(PHP)
↔ Fast API(Python)
↔ Real-ESRGAN(Python)
つまり、手順としては次のとおりです。
- ブラウザで地図を表示する
- ボタンをクリックすると Fast API に画像をアップロード
- Fast API から Real ESRGAN を実行し画像を高画質化
- 保存された画像にブラウザからアクセス
では、楽しんでやっていきましょう
Real-ESRGAN をインストールする
まずは、画像を高画質化する部分Real-ESRGAN
を用意します。
※ なお、おそらくGPU
はなくてもOKだと思いますが、環境によっては実行できない場合もあるかもです。事前に こちら から確認しておいてください。
(お好きな)Python
のフォルダで移動して以下のコマンドを実行します。
git clone https://github.com/xinntao/Real-ESRGAN.git
すると、Real-ESRGAN
というフォルダが作成されますので、以下のコマンドでその中に移動してください。
cd Real-ESRGAN
あとは、以下のコマンドをそれぞれ実行すれば自動的にインストールが完了します。
pip install basicsr
pip install facexlib
pip install gfpgan
pip install -r requirements.txt
python setup.py develop
では、この状態で以下のコマンドを実行してみましょう。
サンプルの画像を高画質化することができます。
python inference_realesrgan.py -n RealESRGAN_x4plus -i inputs
すると、results
フォルダの中に高画質化された画像が保存されることになります。ぜひご自身でチェックしてみてください。
Fast API をインストールする
続いて、Python
で簡単にAPI
を作成することができるFast API
を使って「画像アップロード&高画質化」する部分を作っていきましょう。
まずは必要なパッケージをインストールしておきます。
以下のコマンドを実行してください。
pip install fastapi
pip install uvicorn
では、今回は「high_resolution_map」というフォルダをつくり、その中へコードをつくっていくことにします。
では、「high_resolution_map」フォルダの中へ以下のコードを保存してください。
high_resolution_map/api.py
from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
import os, uuid
app = FastAPI()
origins = [
"http://(Laravel側のドメイン)",
# 必要なだけ追加できます
]
app.add_middleware(
CORSMiddleware, # クロスドメイン対策
allow_origins=origins,
)
@app.post("/high_resolution")
async def create_upload_file(map_image: UploadFile = File(...)):
content = await map_image.read()
# 画像をディスクに保存
random_filename = uuid.uuid4().hex
save_dir = "./uploaded_images"
save_filename = random_filename + ".jpg"
os.makedirs(save_dir, exist_ok=True)
with open(os.path.join(save_dir, save_filename), "wb") as f:
f.write(content)
# 高画質化
original_image_dir = os.getcwd() + "/uploaded_images"
generated_image_dir = "(Laravel までのパス)/public/images/high_resolution_map" # Laravel の公開フォルダ
result = os.system(
"python /(Python プロジェクトまでのパス)/python/Real-ESRGAN/inference_realesrgan.py "+
"-n RealESRGAN_x4plus "+
"-i "+ original_image_dir +" "+
"-o "+ generated_image_dir
)
os.remove(original_image_dir + "/" + save_filename) # 元画像は削除する
result_value = True if result == 0 else False
generated_filename = random_filename + "_out.jpg"
return {
"result": result_value,
"filename": generated_filename,
}
この中では、送信されてきた画像を一旦フォルダに保存し、その画像をReal-ESRGAN
で高画質化し、さらにその画像をLaravel
のフォルダに保存しています。
また、重要なところは以下のとおりです。
クロスドメイン対策
とてもシンプルに言うと、セキュリティ上の理由から「http://example.com」からAjax
で「http://example.net」にアクセスはできません。
でも、これだと自分の別サイトにもアクセスできなくなってしまうので、回避する方法が用意されています。それを行っているのが以下の部分です。
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # クロスドメイン対策
)
つまり、「このorigins
の中に入っているURL
からのアクセスは許可するよ」という意味になります。
高画質化
先ほどReal-ESRGAN
を実行したときは相対パスを使っていたのでコマンドが短かったですが、Fast API
とはまた別のフォルダになりますので、ここでは絶対パスが必要になります。
つまり、コード内の以下2つはご自身のパスの置き換えてください
- (Laravel までのパス)
- (Python プロジェクトまでのパス)
例: /home/*******/pytyhon
Laravel で地図を表示し、高画質化できるようにする
では、やっとLaravel
にたどり着きました。
では、以下のファイルを作成してください。
※ ちなみに地図の場所は、1960年ごろの東京スカイツリーの場所です。
※ CDN
を使っているのでパッケージのインストールは不要です
resources/views/high_resolution_map/index.blade.php
<!DOCTYPE html>
<html>
<head>
<title>地図の高解像度化テスト</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/bootstrap@5.2.3/dist/css/bootstrap.min.css" />
<style>
.loader {
border: 6px solid #f3f3f3;
border-top: 6px solid #3498db;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="p-4">
<h3 class="mb-3">地図の高解像度化テスト</h3>
<div class="row">
<div class="col-6">
<div id="map" class="mb-3" style="width:500px;height:500px;"></div>
<button id="button" type="button" class="btn btn-primary btn-sm">高解像度化する</button>
</div>
<div class="col-6">
<!-- 高解像度化した画像を表示する -->
<img id="generate-image" src="" class="d-none w-100">
<div id="loader" class="loader d-none"></div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet-src.js"></script>
<script src="https://unpkg.com/leaflet-simple-map-screenshoter@0.5.0/dist/leaflet-simple-map-screenshoter.js"></script>
<script src="https://unpkg.com/axios@1.4.0/dist/axios.min.js"></script>
<script>
// マップ
const initialLocation = [35.70990329289358, 139.81077267654356]; // 東京スカイツリー
const map = L.map('map').setView(initialLocation, 17);
L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/ort_old10/{z}/{x}/{y}.png', {
minZoom: 10,
maxZoom: 17,
attribution: '<a href="https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html">国土地理院</a>'
})
.addTo(map);
// プラグイン
const pluginOptions = { hidden: true }; // ボタンは表示しない
const screenShot = L.simpleMapScreenshoter(pluginOptions).addTo(map);
const screenShotOptions = { mimeType: 'image/jpeg' };
// 高解像度化
const button = document.getElementById('button');
button.addEventListener('click', () => {
screenShot.takeScreen('blob', screenShotOptions)
.then(blob => {
const loaderEl = document.getElementById('loader');
const imageEl = document.getElementById('generate-image');
loaderEl.classList.remove('d-none');
imageEl.classList.add('d-none');
const file = new File([blob], 'map.jpg', { type: 'image/jpeg' });
const url = '(Fast API のURL)/high_resolution'; // Fast API の URL
const headers = { 'Content-Type': 'multipart/form-data' };
const formData = new FormData();
formData.append('map_image', file);
axios.post(url, formData, { headers })
.then(response => {
if(response.data.result === true) {
const filename = response.data.filename;
imageEl.src = `/images/high_resolution_map/${filename}`;
imageEl.classList.remove('d-none');
} else {
console.log('高解像度化に失敗しました。');
}
})
.finally(() => {
loaderEl.classList.add('d-none');
})
});
});
</script>
</body>
</html>
この中で重要なのが「地図(航空写真)の画像データ化」です。
ありがたいことに地図ライブラリLeaflet
には simple-map-screenshoter というプラグインがあり、これを使うと自分で実装せずに画像データが取得できるんですね。
そして、その場所が以下の部分です。
screenShot.takeScreen('blob', screenShotOptions)
.then(blob => {
// 省略
});
そして、取得されたblob
データをFile
データに変換しています。(このFile
データは、<input type="file">
でファイル選択されたときのものと同じです)
const file = new File([blob], 'map.jpg', { type: 'image/jpeg' });
最後に、このデータをパラメータとしてFast API
のURL
へ送信し、高画像化が完了したらそのファイル名が取得できるので、それを使って画像を表示するという流れになっています。
ちなみに、(Fast API のURL)となっているところは、Fast API
を起動したときに表示されるURLをセットします。(ポートの変更は「テストしてみる」をご覧ください)
では、これで作業は完了です。
お疲れ様でした
テストしてみる
では、実際にテストしてみましょう
まずは、high_resolution_map
フォルダにコマンド移動し、以下のコマンドを実行します。
uvicorn api:app --reload
※ api
という部分はapi.py
から来ています。ファイル名が別の場合は適宜変更してください。
なお、このコマンドだと127.0.0.1:8000
がアクセスできるURLになりますが、私の場合は別のプロセスが実行中でしたので、以下のようにポート番号4989
(四苦八苦)を使いました。
uvicorn api:app --port 4989 --reload
なお、--reload
は便利なオプションで、もしファイルに変更があったら自動的にサーバーを再起動してくれる(つまり、いちいちストップ&スタートしなくていい)というものになっています。
では、Fast API
が起動したら次はブラウザで「http://******/high_resolution_map」へアクセスします。
※ アクセスするドメインは「クロスドメイン対策」で許可したドメインです。
はい予定通り地図が表示されています。
では、「高解像度化する」ボタンをクリックしてみましょう。
すると・・・・・・
処理中のローディングが表示されています。
そして・・・・・・・・・・
はい
高解像度化された画像が表示されました。
成功です
ちなみに以下が元画像と高解像度化した画像(縮小してますが、その他の加工はしてません)
【元画像】
【高画質化した画像】
※ この画像は国土地理院が提供しているものです
だいぶくっきりはっきりしていますね。
なんと画像サイズは「500px→1980px」です。
Real-ESRGAN
すごいですね
企業様へのご提案
今回は地図(航空写真)の高画質化でしたが、もちろん以下のようなケースでも利用ができます。
- 過去の写真を高画質化する
- 小さな集合写真を高画質化する
- 通常の素材写真を高画質化して大きくする
- フォーカスがぼけた写真を高画質化する
などなど。
もしこういった機能をご希望でしたらお問い合わせからお気軽にご相談ください。
お待ちしております。
おわりに
ということで、今回は「Laravel + Fast API + Real-ESRGAN」で地図画像の高画質化をしてみました。
今回の機能自体はあまり実用的ではないかもしれませんが、いろいろなテクノロジーを組み合わせると面白いことができたりすることを体験していただけると嬉しいです。
それにしてもFast API
は初めて使いましたがホントに「Fast」でした。パッケージをインストールして関数をつくるだけでOK
ですからね
今後、Python
との連携が必要な場合はFast API
を使っていきたいと思います。(好みでしょうが、django
はクセがあまりに強いので…)
ではでは〜
「合体式マットを立体にして
頭を囲み、
質のいい睡眠をゲット」