Laravel ile QR kod okuma ve görsel işleme kullanarak okuma oranını arttırma

Oğuzhan KARACABAY
6 min readNov 29, 2022

--

Merhaba, bu yazımda Laravel ile QR kodlu bir belgeden QR kodu nasıl kesip çıkarabileceğimize, bu kesilen kısımda ki QR kodu nasıl okuyabileceğimize ve okuma oranını nasıl arttırabileceğimize değineceğim.

Laravel projesi oluşturma

Bilgisayarınızda composer kurulu ise aşağıdaki şekilde bir Laravel projesi oluşturabilirsiniz, eğer Composer yüklü değilse yüklemeniz gerekmektedir.

composer create-project laravel/laravel qr-project

Kullanmamız gereken paketleri Composer ile aşağıdaki şekilde kurabiliriz:

composer require simplesoftwareio/simple-qrcode "~4"
composer require tarfin-labs/zbar-php
composer require laraveldaily/laravel-invoices:^3.0

laravel-invoices paketinin kurulumlarını yapmayı unutmuyoruz:

php artisan invoices:install
php artisan invoices:update

Ayrıca paketlerden bazılarını çalıştırabilmek için zbar ve imagemagick toollarının kurulumlarını da yapmalısınız, zbar toolu sayesinde QR kod okuma işlemlerini, imagemagick sayesinde de görüntü işleme işlemlerini yapabileceğiz, aşağıdaki linklerden kurulum sayfalarına ulaşabilirsiniz:

macOS ve Homebrew kullananlar aşağıdaki bash komutlarını kullanarak her iki Tool’u da rahatlıkla yükleyebilirler:

brew install pkg-config imagemagick
brew install zbar

Imagemagick kurulumunu yaptıktan sonra ilgili PHP Extension’unu kurmayı da unutmamak gerekmekte, PECL kullanarak extension kurulumunu da yapıyoruz:

/opt/homebrew/opt/php@8.1/bin/pecl install imagick

Ben Homebrew kullandığım için php/bin konumum yukarıdaki şekilde, sizin farklı olabilir, bu yüzden konumu kendinize göre düzenlemeniz gerekmekte.

Kurulum işlemlerimiz bittikten sonra herhangi bir IDE yardımı ile projemizi açalım. Fatura oluşturma, görüntüleme gibi işlemlerimizi yapabilmek adına app dizini altında Services adlı bir klasör oluşturalım ve bu klasörün içerisine de InvoiceService ve Facades adlı iki klasör daha oluşturalım. İşlem bittikten sonra dizin yapımız aşağıdaki gibi olmalı:

Daha sonra InvoiceService klasörümüzün altına InvoiceService adlı boş bir class oluşturalım ardından InvoiceServiceProvider adlı bir class daha oluşturalım ve içeriğini aşağıdaki şekilde değiştirelim:

<?php

namespace App\Services\InvoiceService;

use Illuminate\Support\ServiceProvider;

class InvoiceServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(InvoiceService::class, function ($app) {
return new InvoiceService();
});
}
}

Böylelikle InvoiceService class’ımız için bir singleton oluşturmuş olduk, bu singleton sayesinde class’ımızda ki tüm fonksiyonlara sanki birer static fonksiyonmuş gibi ulaşıp kullanabileceğiz, geriye sadece Facade oluşturmak kaldı, Services/Facades adlı klasörümüzün içerisine InvoiceService adlı bir php classı oluşturalım:

Artık ServiceProvider’imizi Laravel’e tanıtıp projenin her yerinden InvoiceService class’ımıza singleton olarak erişebiliriz.

ServiceProvider’i Laravel’e tanıtmak için config/app.php açalım ve aşağıdaki şekilde ekleyelim:

Son olarak Facade’mizi de projeye tanıtıp bitiriyoruz:

InvoiceService oluşturma

Artık InvoiceService classımızı yazmaya başlayabiliriz, öncelikle sıradan bir fatura belgesi oluşturma fonksiyonu yazalım:

<?php

namespace App\Services\InvoiceService;

use Illuminate\Http\Response;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Invoice;

class InvoiceService
{
/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Exception
*/
public function createInvoiceAsPdf(): string
{
$customer = new Buyer([
'name' => 'John Doe',
'custom_fields' => [
'email' => 'test@example.com',
],
]);

$item = (new InvoiceItem())->title('Service 1')->pricePerUnit(2);

$invoice = Invoice::make()
->buyer($customer)
->discountByPercent(10)
->taxRate(15)
->shipping(2)
->addItem($item);

return $invoice->render()->output;
}
}

Bu işlemden sonra InvoiceService::createInvoiceAsPdf() şeklinde fonksiyonumuza erişebiliriz.

InvoiceController oluşturma

Aşağıdaki artisan komutunu terminalde çalıştıralım:

php artisan make:controller InvoiceController --invokable

Daha sonra oluşan InvoiceController classını aşağıdaki şekilde kodlayalım:

<?php

namespace App\Http\Controllers;

use App\Services\Facades\InvoiceService;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;

class InvoiceController extends Controller
{
public function __invoke()
{
return response()
->streamDownload(
callback: function (): void {
echo InvoiceService::createInvoiceAsPdf();
},
name: 'invoice.pdf'
);
}
}

Route oluşturma

routes/web.php dosyasını açalım ve aşağıdaki şekilde değiştirelim:

<?php

use App\Http\Controllers\InvoiceController;
use Illuminate\Support\Facades\Route;

Route::get('/get-invoice', InvoiceController::class);

Artık projemizde get-invoice URL’ine girdiğimizde indirilecek dosyanın aşağıdaki gibi olmasını bekliyoruz:

Faturaya QR kod ekleme

Başarılı bir şekilde faturamızı oluşturduk, sırada faturamıza QR kod eklemek olacak, ilk olarak InvoiceService classımızı açıp fatura template’imize custom bir order_id ve qr_data değişkeni ekliyoruz:

<?php

namespace App\Services\InvoiceService;

use Illuminate\Http\Response;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Invoice;
use SimpleSoftwareIO\QrCode\Facades\QrCode;

class InvoiceService
{
/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Exception
*/
public function createInvoiceAsPdf(): string
{
$customer = new Buyer([
'name' => 'John Doe',
'custom_fields' => [
'email' => 'test@example.com',
],
]);

$item = (new InvoiceItem())->title('Service 1')->pricePerUnit(2);

$invoice = Invoice::make()
->buyer($customer)
->discountByPercent(10)
->taxRate(15)
->shipping(2)
->addItem($item);

$invoice->setCustomData([
'order_id' => $orderId = random_int(100000,999999),
'qr_data' => base64_encode(QrCode::format('png')->size(100)->generate($orderId)),
]);

return $invoice->render()->output;
}
}

Daha sonra resources/views/vendor/invoices/templates/default.blade.php yolunda bulunan blade dosyamızı açıp aşağıdaki şekilde bir ekleme yapıyoruz:

        {{-- Header --}}
@if($invoice->logo)
<img src="{{ $invoice->getLogo() }}" alt="logo" height="100">
@endif

<p class="text-right m-5">
<img
src="data:image/png;base64,
{!! $invoice->getCustomData()['qr_data'] !!} "
>
</p>

Style kısmına da bu class’ı ekliyoruz:

       .m-5 {
margin-right: 3rem !important;
margin-top: 3rem !important;
}

Dosyamızda ki değişiklikleri kaydedip tekrar invoice oluşturduğumuzda aşağıdaki gibi bir görüntü görmüş olmalıyız:

Faturadan QR kod okuma

Öncelikle oluşturduğumuz faturayı dosya yolu storage/app/public/invoice.pdf olacak şekilde kopyalayalım.

Şimdi sırada oluşturmuş olduğumuz invoice dosyasından bu QR kodu kesip çıkararak taratmak olacak. Eğer QR kod kısmını kesmeden okutmaya çalışırsak tüm dosyanın içerisinde QR kodu yakalama olasılığı oldukça azalmakta, bu yüzden kesme işlemini gerçekleştirmemiz gerekiyor. Aşağıdaki şekilde bir yapı oluşturalım:

config/app.php içerisine ServiceProvider’imizi ve Facade aliasımızı eklemeyi unutmayalım.

ImageProcessingService class’ımızı açarak aşağıdaki şekilde düzenleyelim:

<?php

namespace App\Services\ImageProcessingService;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Imagick;
use TarfinLabs\ZbarPhp\Exceptions\ZbarError;
use TarfinLabs\ZbarPhp\Zbar;

class ImageProcessingService
{
public int $edgeLength = 240;

/**
* @throws \ImagickException
* @throws \TarfinLabs\ZbarPhp\Exceptions\UnableToOpen
* @throws \TarfinLabs\ZbarPhp\Exceptions\InvalidFormat
* @throws \Exception
*/
public function readQrCode(string $filePath): string|null
{
$cropedQRPath = 'public/'.Str::random(40).'.png';
$imagickInstance = $this->cropQrCodeFromPDF($filePath);

Storage::put($cropedQRPath, $imagickInstance->getImageBlob());

try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$readedCode = null;
}

Storage::delete($cropedQRPath);

return $readedCode;
}

/**
* @throws \Exception
*/
public function cropQrCodeFromPDF(string $filePath): Imagick
{
$imagick = new Imagick();
$imagick->setResolution(128, 128);
$imagick->readImageBlob(Storage::get($filePath));

$imageWidth = $imagick->getImageWidth() - $this->edgeLength;
$imagick->cropImage($this->edgeLength + 80, $this->edgeLength + 80, $imageWidth - 80 , 0);
$imagick->resizeImage($this->edgeLength + 80, $this->edgeLength + 80, 1, 0);

$imagick->setImageFormat('png');

return $imagick;
}


}

Classımızı düzenledikten sonra ImageProcessingController adlı bir Controller oluşturalım:

php artisan make:controller ImageProcessingController --invokable

Ve içeriğini aşağıdaki şekilde düzenleyelim:

<?php

namespace App\Http\Controllers;

use App\Services\Facades\ImageProcessingService;
use Illuminate\Http\JsonResponse;

class ImageProcessingController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(): JsonResponse
{
return \response()->json([
'code' => ImageProcessingService::readQrCode('public/invoice.pdf')
]);
}
}

Daha sonra routes/web.php açarak aşağıdaki gibi düzenleyelim:

<?php

use App\Http\Controllers\ImageProcessingController;
use App\Http\Controllers\InvoiceController;
use Illuminate\Support\Facades\Route;

Route::get('/get-invoice', InvoiceController::class);
Route::get('/process-qr-code', ImageProcessingController::class);

Tarayıcımızı açıp /process-qr-code URL’ine girelim ve sonuca bakalım:

Gördüğünüz gibi üretmiş olduğumuz QR kod başarıyla okunarak sonuç Response’da bize iletildi.

Görüntü işleme ile QR okunma oranını arttırma

Günlük hayatta taratılan belgelerde QR kodlar çok okunaklı olmayabiliyor, bu yüzden okunma oranını arttırmak için yapılabilecek bir kaç görüntü işleme tekniği bulunmakta, yapılan işlemlerle ilgili fazla ayrıntıya girmeyeceğim çünkü görüntü işleme kısmını da makalenin içine dahil edersek çok çok uzun bir makale olması gerekeceği kesin :) ama ufak bir şekilde açıklamam gerekirse, yapılan işlemler arka planda ki QR koda ait olmayan pixelleri beyazlaştırıp QR kod kısmını da siyahlaştırarak daha belirgin bir QR kod elde etmemize yardımcı olmakta.

ImageProcessingService class’ımızı açıp readQrCode fonksiyonunu aşağıdaki gibi düzenleyelim:

    /**
* @throws \ImagickException
* @throws \TarfinLabs\ZbarPhp\Exceptions\UnableToOpen
* @throws \TarfinLabs\ZbarPhp\Exceptions\InvalidFormat
* @throws \Exception
*/
public function readQrCode(string $filePath): string|null
{
$cropedQRPath = 'public/'.Str::random(40).'.png';
$imagickInstance = $this->cropQrCodeFromPDF($filePath);

Storage::put($cropedQRPath, $imagickInstance->getImageBlob());

try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$imagickInstance->setImageBackgroundColor('#FFFFFF');
$imagickInstance->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
$imagickInstance->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
$imagickInstance->autoLevelImage();

$imagickInstance->autoThresholdImage(1); // 1 means we choose OTSU threshold method

$kernel = ImagickKernel::fromBuiltIn(Imagick::KERNEL_SQUARE, '1');
$imagickInstance->morphology(\Imagick::MORPHOLOGY_OPEN, 1, $kernel);

Storage::disk('local')->put($cropedQRPath, $imagickInstance->getImageBlob());

try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$readedCode = null;
}
}

Storage::delete($cropedQRPath);

return $readedCode;
}

Bu işlemleri catch (ZbarError) kısmından sonra yaptırmamızın sebebi, eğer ki QR kod okuma başarılı ise ekstradan işlem yaptırmamıza gerek olmaması, eğer QR kodu okuyup okumada başarısız olduysak gerekli görüntü işleme algoritmalarının çalışması bize zaman kazandıracaktır.

Bu makaleyi hem Türkçe hem de İngilizce dillerinde okuyabilirsiniz.

--

--

No responses yet