Selfie Printing Camera using GR-LYCHEE with OpenCV
mbed祭り 2019@春の名古屋

デモ機の説明
自分の顔をLCDに表示させ、mbedシールを張った札を数秒静止させると、サーマルプリンタで印字する自撮りするデモです。
- 画像をグレースケール化
- 平滑化
- ハフ変換による円の検出
- 検出円の中心の少し上のBGR値取得しHSV値に変換
- HSV値が青っぽい色だとmbedシール判定
という流れです。GR-LYCHEEのUSR_BUTTON0でサーマルプリンタへの出力をON/OFFできます。GR-LYCHEEには3MBのRAMがあるのですがHEAP領域でないRAM_NC領域1MBも活用しています。
Mbec CLIとOpenCV環境を整える
- GR-LYCHEE用オフライン開発環境の手順
- OpenCVサンプルメモを参照しGR-Boards_NonContactMouseをインポートすることでOpenCVの環境が整います
構成
- GR-LYCHEE and Camera
- LCD(ATM0430D25)
- AS-289R2 Thermal Printer Shield
include the mbed library with this snippet
#include "mbed.h"
#include "EasyAttach_CameraAndLCD.h"
#include "opencv.hpp"
#include "SdUsbConnect.h"
#include "AS289R2.h"
#include <vector>
#define VIDEO_PIXEL_HW (480u) /* WQVGA */
#define VIDEO_PIXEL_VW (272u) /* WQVGA */
/*! Frame buffer stride: Frame buffer stride should be set to a multiple of 32 or 128
in accordance with the frame buffer burst transfer mode. */
#define DATA_SIZE_PER_PIC (2u)
#define FRAME_BUFFER_STRIDE (((VIDEO_PIXEL_HW * DATA_SIZE_PER_PIC) + 31u) & ~31u)
#define FRAME_BUFFER_STRIDE_3 (((VIDEO_PIXEL_HW * 3u) + 31u) & ~31u)
#define FRAME_BUFFER_STRIDE_1 ((VIDEO_PIXEL_HW + 31u) & ~31u)
#define FRAME_BUFFER_HEIGHT (VIDEO_PIXEL_VW)
static uint8_t FrameBuffer_Video[FRAME_BUFFER_STRIDE * FRAME_BUFFER_HEIGHT]__attribute((section("NC_BSS"),aligned(32)));
static uint8_t FrameBuffer_Gray[FRAME_BUFFER_STRIDE_1 * FRAME_BUFFER_HEIGHT]__attribute((section("NC_BSS"),aligned(32)));
static uint8_t FrameBuffer_Result[FRAME_BUFFER_STRIDE * FRAME_BUFFER_HEIGHT]__attribute((section("NC_BSS"),aligned(32)));
static uint8_t FrameBuffer_BGR[FRAME_BUFFER_STRIDE_3 * FRAME_BUFFER_HEIGHT]__attribute((section("NC_BSS"),aligned(32)));
static DisplayBase Display;
static cv::Mat src_img(VIDEO_PIXEL_VW, VIDEO_PIXEL_HW, CV_8UC2, FrameBuffer_Video);
static cv::Mat gray_img(VIDEO_PIXEL_VW, VIDEO_PIXEL_HW, CV_8UC1, FrameBuffer_Gray);
static cv::Mat result_img(VIDEO_PIXEL_VW, VIDEO_PIXEL_HW, CV_8UC2, FrameBuffer_Result);
static InterruptIn btn0(USER_BUTTON0);
static int btn0_type = 0;
#define BTN0_TYPE_MAX (1)
DigitalOut myLed1(LED1);
DigitalOut myLed2(LED2);
DigitalOut myLed3(LED3);
DigitalOut myLed4(LED4);
int center_px_bak = 0;
int center_py_bak = 0;
int lock_ct = 0;
#define LOCK_CT_MAX (5)
AS289R2 tp(D1, 57600);
Thread thread;
static void camera_start(void)
{
// Camera
EasyAttach_Init(Display, VIDEO_PIXEL_HW, VIDEO_PIXEL_VW);
// Video capture setting (progressive form fixed)
Display.Video_Write_Setting(
DisplayBase::VIDEO_INPUT_CHANNEL_0,
DisplayBase::COL_SYS_NTSC_358,
(void *)FrameBuffer_Video,
FRAME_BUFFER_STRIDE,
DisplayBase::VIDEO_FORMAT_YCBCR422,
DisplayBase::WR_RD_WRSWA_32_16BIT,
VIDEO_PIXEL_VW,
VIDEO_PIXEL_HW
);
EasyAttach_CameraStart(Display, DisplayBase::VIDEO_INPUT_CHANNEL_0);
}
static void lcd_start(void)
{
DisplayBase::rect_t rect;
// GRAPHICS_LAYER_0
rect.vs = 0;
rect.vw = VIDEO_PIXEL_VW;
rect.hs = 0;
rect.hw = VIDEO_PIXEL_HW;
Display.Graphics_Read_Setting(
DisplayBase::GRAPHICS_LAYER_0,
(void *)&src_img.data[0],
FRAME_BUFFER_STRIDE,
DisplayBase::GRAPHICS_FORMAT_YCBCR422,
DisplayBase::WR_RD_WRSWA_32_16BIT,
&rect
);
Display.Graphics_Start(DisplayBase::GRAPHICS_LAYER_0);
// GRAPHICS_LAYER_2
rect.vs = 0;
rect.vw = VIDEO_PIXEL_VW;
rect.hs = 0;
rect.hw = VIDEO_PIXEL_HW;
Display.Graphics_Read_Setting(
DisplayBase::GRAPHICS_LAYER_2,
(void *)&result_img.data[0],
FRAME_BUFFER_STRIDE,
DisplayBase::GRAPHICS_FORMAT_ARGB4444,
DisplayBase::WR_RD_WRSWA_32_16BIT,
&rect
);
Display.Graphics_Start(DisplayBase::GRAPHICS_LAYER_2);
Thread::wait(50);
EasyAttach_LcdBacklight(true);
}
static void btn0_fall(void)
{
if ( btn0_type < BTN0_TYPE_MAX ) {
btn0_type++;
}
else {
btn0_type = 0;
}
}
/**
* Image Printing : Mat to AS-289R2
*/
void MatToAS289R2(cv::Mat& gray_img)
{
int imgHeight = gray_img.rows;
int offset = 48;
tp.printf("\x1C\x2A\x65"); // AS-289R2 CMD
tp.putc((uint8_t)(imgHeight / 256));
tp.putc((uint8_t)(imgHeight % 256));
for (int iy = 0 ; iy < imgHeight; iy++) {
for (int ix = 0; ix < 48; ix ++) {
uint8_t pixel8 = 0;
for (int ib = 0; ib < 8; ib ++) {
uint8_t pixel = gray_img.at<uint8_t>( iy, ix * 8 + ib + offset ) ^ 0xFF ;
pixel8 <<= 1;
if (pixel && 0xFF) {
pixel8 |= 1;
}
}
tp.putc( pixel8 );
}
}
}
/**
* Dithering Algorithm
*/
uint8_t saturated_add(uint8_t val1, int8_t val2)
{
int16_t val1_int = val1;
int16_t val2_int = val2;
int16_t tmp = val1_int + val2_int;
if (tmp > 255) {
return 255;
}
else if (tmp < 0) {
return 0;
}
else {
return tmp;
}
}
void Dithering(cv::Mat& gray_img)
{
int dstWidth = gray_img.cols;
int dstHeight = gray_img.rows;
int err;
int8_t a, b, c, d;
for (int i = 0; i < dstHeight; i++) {
for (int j = 0; j < dstWidth; j++) {
if (gray_img.at<uint8_t>( i, j ) > 127) {
err = gray_img.at<uint8_t>(i, j) - 255;
gray_img.at<uint8_t>(i, j) = 255;
}
else {
err = gray_img.at<uint8_t>(i, j) - 0;
gray_img.at<uint8_t>(i, j) = 0;
}
a = (err * 7) / 16;
b = (err * 1) / 16;
c = (err * 5) / 16;
d = (err * 3) / 16;
if ((i != (dstHeight - 1)) && (j != 0) && (j != (dstWidth - 1))) {
gray_img.at<uint8_t>(i + 0, j + 1) = saturated_add( gray_img.at<uint8_t>(i + 0, j + 1), a);
gray_img.at<uint8_t>(i + 1, j + 1) = saturated_add( gray_img.at<uint8_t>(i + 1, j + 1), b);
gray_img.at<uint8_t>(i + 1, j + 0) = saturated_add( gray_img.at<uint8_t>(i + 1, j + 0), c);
gray_img.at<uint8_t>(i + 1, j - 1) = saturated_add( gray_img.at<uint8_t>(i + 1, j - 1), d );
}
}
}
}
void Lockon(int center_px_val, int center_py_val)
{
int tebure = 10;
if (center_px_val < center_px_bak - tebure || center_px_val > center_px_bak + tebure) {
lock_ct = 0;
}
else if (center_py_val < center_py_bak - tebure || center_py_val > center_py_bak + tebure) {
lock_ct = 0;
}
else if (center_px_val == 0 || center_py_val == 0) {
lock_ct = 0;
}
else {
if (lock_ct < LOCK_CT_MAX) lock_ct ++;
}
center_px_bak = center_px_val; center_py_bak = center_py_val;
}
int main() {
btn0.fall(&btn0_fall);
camera_start();
lcd_start();
while (1) {
cv::Mat bgr_img(VIDEO_PIXEL_VW, VIDEO_PIXEL_HW, CV_8UC3, FrameBuffer_BGR);
cv::cvtColor(src_img, bgr_img, cv::COLOR_YUV2BGR_YUYV); // copy camera image to BGR
cv::cvtColor(src_img, gray_img, cv::COLOR_YUV2GRAY_YUY2); // copy camera image to Gray
cv::GaussianBlur(gray_img, gray_img, cv::Size(55, 55), 1); // 平滑化
// ハフ変換による円の検出
vector<cv::Vec3f> circles;
cv::HoughCircles(gray_img, circles, CV_HOUGH_GRADIENT, 2, 30, 200, 200, 20, 130);
// 検出結果バッファのクリア
memset(FrameBuffer_Result, 0, sizeof(FrameBuffer_Result));
cv::putText(result_img, "Detecting Circles in Images using OpenCV.", cv::Point(5, 20), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(0xF0, 0xF0), 1);
// 円描画
vector<cv::Vec3f>::iterator it;
for (it = circles.begin(); it != circles.end(); it++)
{
cv::Point center(cv::saturate_cast<int>((*it)[0]), cv::saturate_cast<int>((*it)[1]));
int radius = cv::saturate_cast<int>((*it)[2]);
// 円の中心より少し上のポイントのHSV値をチェック(注意OpenCVではH値は0-180の範囲。0-360ではない)
int py = center.y - (radius * 2 / 3);
int px = center.x;
cv::Mat ex1_img(1, 1, CV_8UC3, 3);
ex1_img.at<cv::Vec3b>(0, 0) = bgr_img.at<cv::Vec3b>(py, px);
cv::Mat ex2_img(1, 1, CV_8UC3, 3);
cv::cvtColor(ex1_img, ex2_img, cv::COLOR_BGR2HSV); // BGR to HSV
int hsv = ex2_img.at<cv::Vec3b>(0, 0)[0];
printf("xy:%d-%d %d\r\n", center.x, center.y, hsv);
// mbedシールの青色(Hue90-115)
if (hsv > 90 && hsv < 115) {
cv::circle(result_img, center, radius, cv::Scalar(0x19, 0xFF), 3); // 円描画:赤色
cv::putText(result_img, "+", cv::Point(center.x - 10, center.y + 10), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0x19, 0xFF), 2); // 円の中心に+を描画
Lockon(center.x, center.y);
// カウントダウン値を描画
switch (lock_ct) {
case 2:
cv::putText(result_img, "3", cv::Point(center.x - 10, center.y - 25), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0x19, 0xFF), 2);
break;
case 3:
cv::putText(result_img, "2", cv::Point(center.x - 10, center.y - 25), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0x19, 0xFF), 2);
break;
case 4:
cv::putText(result_img, "1", cv::Point(center.x - 10, center.y - 25), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0x19, 0xFF), 2);
break;
default:
break;
}
}
else {
cv::circle(result_img, center, radius, cv::Scalar(0xF0, 0xF0), 3); // 円描画:緑色
cv::putText(result_img, "+", cv::Point(center.x - 10, center.y + 10), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0xF0, 0xF0), 2);
Lockon(0, 0);
}
}
// Printing
if (btn0_type == 0 && lock_ct >= LOCK_CT_MAX) {
tp.printf( " mbed祭り 2019@春の名古屋\r" );
tp.putPaperFeed(15);
tp.printf( " いぶし銀カメラ\r" );
tp.putPaperFeed(15);
tp.printf( " GR-LYCHEE+カメラ+OpenCV\r" );
tp.putPaperFeed(15);
Dithering(gray_img);
MatToAS289R2(gray_img);
tp.putPaperFeed(15);
tp.printf( "#mbed_fest SPRING 2019 NAGOYA" );
tp.putPaperFeed(15);
tp.printf( " Selfie Printing Camera\r" );
tp.putPaperFeed(15);
tp.printf( " using GR-LYCHEE with OpenCV\r" );
tp.putLineFeed(7);
lock_ct = 0;
}
if (btn0_type == 0) {
myLed4 = 1;
}
else {
myLed4 = 0;
}
}
}
1 comment on Selfie Printing Camera using GR-LYCHEE with OpenCV:
Please log in to post comments.

名古屋、お疲れ様です。 サーマルプリンタ、自宅のおもちゃに繋ぎたくなってきた。。。