#include <minimgapi/imgguard.hpp>
#include <minimgapi/minimgapi.h>
#include <minimgprc/minimgprc.h>
#include <minutils/minerr.h>
#include <se-dbg/se-dbg.hpp>
#include "segmentation.h"
#include <cstring>
#include <sstream>
namespace prodmark {
enum SIZE_CODES {
SMALL_SIZE = 1,
NORMAL_SIZE = 2,
BIG_SIZE = 4,
VERY_BIG_SIZE = 8,
};
static int DrawRects (const MinImg *image,
const std::vector<MinRect> &rects,
uint8_t *color) {
for (size_t i = 0; i < rects.size (); i++) {
const MinRect &rect = rects[i];
uint8_t *top_line = image->pScan0 + rect.y * image->stride;
uint8_t *bottom_line = image->pScan0 + (rect.y + rect.height) * image->stride;
for (int x = rect.x; x < rect.x + rect.width; x++) {
memcpy (top_line + x * image->channels, color, image->channels);
memcpy (bottom_line + x * image->channels, color, image->channels);
}
for (int y = rect.y; y < rect.y + rect.height; y++) {
uint8_t *line = image->pScan0 + y * image->stride;
memcpy (line + rect.x * image->channels, color, image->channels);
memcpy (line + (rect.x + rect.width) * image->channels, color, image->channels);
}
}
return NO_ERRORS;
}
static int AddCellPadding(std::vector<MinRect> &rects,
const MinImg *zone,
const int extra_pad_x,
const int extra_pad_y) {
for (size_t i_cell(0); i_cell < rects.size(); i_cell += 1) {
MinRect& rc(rects[i_cell]);
rc.x -= extra_pad_x;
rc.y -= extra_pad_y;
rc.width += 2 * extra_pad_x;
rc.height += 2 * extra_pad_y;
if (rc.x < 0) {
rc.width += rc.x;
rc.x = 0;
}
if (rc.y < 0) {
rc.height += rc.y;
rc.y = 0;
}
if (zone->width <= rc.x + rc.width) {
rc.width = zone->width - rc.x - 1;
}
if (zone->height <= rc.y + rc.height) {
rc.height = zone->height - rc.y - 1;
}
}
return NO_ERRORS;
}
static int HorizontalSize (const Segment &seg,
int height,
const EngineSettings& set) {
if (seg.width < set.seg_small_size * height)
return SMALL_SIZE;
else if (seg.width > set.seg_very_big_size * height)
return VERY_BIG_SIZE;
return (seg.width > set.seg_normal_size)? BIG_SIZE : NORMAL_SIZE;
}
static bool isSmallGarbage (const MinImg *zone,
MinRect &rect,
int thr,
const MinImg *zone_image,
const EngineSettings& set) {
if (HorizontalSize (Segment (0, rect.width), rect.height, set) == SMALL_SIZE)
return true;
std::vector<bool> p (rect.height, false);
int d = 2;
for (int y = rect.y; y < rect.y + rect.height; y++)
for (int x = rect.x + d; x < rect.x + rect.width - 2 * d; x++)
if (zone->pScan0[y * zone->stride + x] < thr)
p[y - rect.y] = true;
int b = 0;
for (size_t t = 0; t < p.size(); ++t) {
if (p[t]) {
b++;
}
}
return 2 * b + 10 < zone_image->height;
}
static bool isNotBlank (const MinImg *zone,
MinRect &rect,
int thr,
const EngineSettings& set) {
int n = 0;
for (int y = rect.y; y < rect.y + rect.height; y++) {
for (int x = rect.x; x < rect.x + rect.width; x++) {
if (zone->pScan0[y * zone->stride + x] < thr) {
n++;
}
}
}
return (set.seg_threshold * n > rect.width * rect.height);
}
int FindBinarizationLevel (int &level, const MinImg *line) {
uint8_t bounds[] = {0, 255};
DECLARE_GUARDED_MINIMG(hist);
PROPAGATE_ERROR(NewMinImagePrototype(&hist, 256, 1, 1, TYP_UINT32));
PROPAGATE_ERROR (Compute1ChImageHistogram (&hist, bounds, line));
OtsuLevel otsu_level;
PROPAGATE_ERROR (ComputeOtsuLevel (&otsu_level, &hist, 0, 255));
level = otsu_level.level;
return NO_ERRORS;
}
static int FindBaseLines (const MinImg *zone,
int threshold,
int &height,
int &top) {
int dw = (int)(0.1 * zone->width);
int dh = (int)(0.1 * zone->height);
std::vector<int> proj (zone->height, 0);
for (int y = dh; y < zone->height - dh; ++y) {
const uint8_t *line = zone->pScan0 + y * zone->stride;
for (int x = dw; x < zone->width - dw; ++x)
if (line[x] <= threshold)
proj[y]++;
}
top = (zone->height - dh) / 4;
height = (zone->height - dh) / 2;
int best_score = 0;
for (size_t i = top; i < top + height; i++)
best_score += proj[i];
for ( ; height < zone->height - dh; height++) {
int up = (top > 0)? proj[top - 1] : 0;
int dw = (top + height < zone->height - dh)? proj[top + height - dh] : 0;
if (std::max (up, dw) * height * 4.0 < best_score)
break;
best_score += std::max (up, dw);
if (up > dw)
top--;
}
return NO_ERRORS;
}
static int FinalCorrection (const MinImg *zone,
MinRect &rect,
int height,
int thr) {
std::vector<bool> projy (zone->height, false);
for (int y = 0; y < zone->height; y++) {
const uint8_t *line = zone->pScan0 + y * zone->stride;
for (int x = rect.x; x < rect.x + rect.width; x++) {
if (line[x] < thr) {
projy[y] = true;
break;
}
}
}
for (int y = rect.y - 1; y >= -1; y--) {
if (y < 0 || !projy[y]) {
rect.height += rect.y - y - 1;
rect.y = y + 1;
break;
}
}
for (int y = rect.y + rect.height; y <= zone->height; y++) {
if (y == zone->height || !projy[y]) {
rect.height = y - rect.y;
break;
}
}
for (int y = rect.y; rect.height > height / 3; y++) {
if (!projy[y]) {
rect.height--;
rect.y++;
} else {
break;
}
}
for (int y = rect.y + rect.height - 1; rect.height > height / 3; y--) {
if (!projy[y])
rect.height--;
else
break;
}
if (rect.height < height / 2)
return NO_ERRORS;
int rx = rect.x;
int rw = rect.width;
std::vector<int> projx (rect.width, false);
for (int x = rx; x < rx + rect.width; x++) {
for (int y = rect.y; y < rect.y + rect.height; y++) {
const uint8_t *line = zone->pScan0 + y * zone->stride;
if (line[x] < thr) {
projx[x - rx] = true;
break;
}
}
}
for (int x = rx; rect.width > height / 3; x++) {
if (!projx[x - rx]) {
rect.width--;
rect.x++;
} else {
break;
}
}
if (rect.x > rx) {
rect.x--;
rect.width++;
}
for (int x = rect.x + rect.width - 1; rect.width > height / 3; x--) {
if (!projx[x - rx])
rect.width--;
else
break;
}
if (rx + rw > rect.x + rect.width)
rect.width++;
return NO_ERRORS;
}
static int HorizontalCorrection (std::vector<MinRect> &rects, int height) {
if (rects.size() > 0) {
std::vector<MinRect> rects1 (1, rects[0]);
for (size_t i = 1; i < rects.size (); i++) {
if (rects1.back ().width + rects[i].width <= 5)
rects1.back ().width += rects[i].width;
else if (rects[i].width <= 5 && i < rects.size () - 1) {
int d = rects[i].width / 2;
rects1.back ().width += d;
rects[i + 1].x -= rects[i].width - d;
rects[i + 1].width += rects[i].width - d;
} else {
int d = (rects[i].x - (rects1.back ().x + rects1.back ().width - 1));
if (2 * d <= height) {
rects1.back ().width += d / 2;
rects1.push_back (MinRect (rects[i].x - d + d / 2, rects[i].y, rects[i].width + d - d / 2, rects[i].height));
} else {
rects1.push_back (MinRect (rects1.back ().x + rects1.back ().width - 1, rects[i].y,
rects[i].x - rects1.back ().x - rects1.back ().width, rects[i].height));
i--;
}
}
}
rects = rects1;
}
return NO_ERRORS;
}
static void FindSegments (const MinImg *zone,
const std::vector<uint8_t> &projx,
const Segment &seg,
int threshold,
int height,
std::vector<Segment> &segs) {
int beg = 0;
bool find_black = true;
for (size_t i = seg.x; i <= seg.x + seg.width; i++) {
if (find_black && projx[i] <= threshold) {
beg = i;
find_black = false;
} else if (!find_black && (i == seg.x + seg.width || projx[i] > threshold)) {
segs.push_back (Segment (beg, i - beg));
find_black = true;
}
}
}
static int CuttingSegment (const MinImg *zone,
const std::vector<uint8_t> &projx,
const Segment &seg,
uint8_t threshold,
int level,
std::vector<Segment> &segs,
const EngineSettings& set) {
uint8_t mx = 0;
for (int i = seg.x; i < seg.x + seg.width; i++)
mx = std::max (mx, projx[i]);
if (mx > set.seg_lower_threshold && threshold - mx < set.seg_upper_threshold) {
FindSegments (zone, projx, seg, std::min(mx, threshold) - 1, zone->height, segs);
std::vector<Segment> segs1;
for (size_t i = 0; i < segs.size (); ++i) {
if (HorizontalSize (segs[i], zone->height, set) >= BIG_SIZE) {
std::vector<Segment> segs2;
CuttingSegment (zone, projx, segs[i], threshold, level + 1, segs2, set);
for (size_t j = 0; j < segs2.size (); ++j)
segs1.push_back (segs2[j]);
} else {
segs1.push_back (segs[i]);
}
}
segs = segs1;
} else {
segs.push_back (seg);
}
return NO_ERRORS;
}
static int Cutting (const MinImg *zone,
const int threshold,
std::vector<Segment> &segs,
std::vector<uint8_t> &projx,
const EngineSettings& set) {
for (int y = 0; y < zone->height; y++) {
const uint8_t *line = zone->pScan0 + y * zone->stride;
for (int x = 0; x < zone->width; x++)
projx[x] = std::min (projx[x], line[x]);
}
return CuttingSegment (zone, projx, Segment(0, zone->width), threshold, 0, segs, set);
}
int Segmentation (SegmentationResult &segmentation_result,
const MinImg* zone_image,
const EngineSettings& set,
std::string image_name) {
segmentation_result.rects_.resize(0);
std::vector<Segment> segs;
std::vector<uint8_t> projx (zone_image->width + 1, 255);
uint8_t black = 0;
int threshold = 0;
MinImg input = {0};
DECLARE_GUARDED_MINIMG(output);
int height = 0, top = 0;
PROPAGATE_ERROR(FindBinarizationLevel(threshold, zone_image));
PROPAGATE_ERROR(FindBaseLines(zone_image, threshold, height, top));
PROPAGATE_ERROR(GetMinImageRegion(&input, zone_image, 5, top, zone_image->width - 10, height));
PROPAGATE_ERROR(CloneMinImagePrototype(&output, &input));
PROPAGATE_ERROR(CopyMinImage(&output, &input));
PROPAGATE_ERROR(CloseImage(&output, &input, 20, 20, BO_REPEAT));
PROPAGATE_ERROR(MergeImage(&output, &output, &input, BIOP_DIF));
PROPAGATE_ERROR(LinTransformImage(&output, &output, -1.0, 255.0));
PROPAGATE_ERROR(DilateImage(&output, &output, 1, 1, BO_REPEAT));
PROPAGATE_ERROR(FindBinarizationLevel(threshold, &output));
PROPAGATE_ERROR(Cutting(&output, threshold, segs, projx, set));
std::vector <MinRect> rects(segs.size());
for (size_t i = 0; i < segs.size(); ++i) {
rects[i] = MinRect(segs[i].x, 0, segs[i].width, height);
}
PROPAGATE_ERROR(HorizontalCorrection(rects, height));
for (size_t i = 0; i < rects.size(); ++i) {
PROPAGATE_ERROR(FinalCorrection(&output, rects[i], height, threshold));
}
//PROPAGATE_ERROR(AddCellPadding(rects, zone_image, 2, 2));
for (size_t i = 0; i < rects.size(); ++i) {
if (!isNotBlank(&output, rects[i], threshold, set)) {
rects.erase(rects.begin() + i);
i--;
}
}
for (size_t i = 0; i < rects.size(); ++i) {
if (isSmallGarbage(&output, rects[i], threshold, zone_image, set)) {
rects.erase(rects.begin() + i);
i--;
}
}
for (size_t i = 0; i < rects.size(); ++i) {
rects[i].y += top;
rects[i].x += set.seg_return_size;
if (rects[i].y + rects[i].height == zone_image->height) {
rects[i].height--;
}
}
for (size_t i = 0; i < rects.size(); ++i) {
if (rects[i].width < set.seg_fix_size) {
rects[i].x -= (set.seg_fix_size - rects[i].width) / 2;
rects[i].width += set.seg_fix_size - rects[i].width;
}
}
if (rects.size() > set.seg_max_number) {
for (size_t i = 0; i < rects.size() - 1; ++i) {
if (rects[i].width < set.seg_both_small && rects[i + 1].width < set.seg_both_small &&
rects[i + 1].x - rects[i].x - rects[i].width < set.seg_dist) {
rects[i].width += rects[i + 1].width;
rects.erase(rects.begin() + i + 1);
}
}
}
//DrawRects(zone_image, rects, &black);
for (size_t i = 0; i < rects.size(); ++i) {
segmentation_result.rects_.push_back(rects[i]);
}
std::string debug_images_dir = "C:\\workspace\\tag\\prodmark\\debug_images_dir";
std::string names[100];
for (int i = 0; i < 100; ++i) {
char buffer[30] = {0};
itoa(i, buffer, 10);
for (int j = 0; j < 30; ++j) {
if (buffer[j] != 0) {
names[i] += buffer[i];
}
}
}
image_name.pop_back();
image_name.pop_back();
image_name.pop_back();
image_name.pop_back();
for (int i = 0; i < rects.size(); ++i) {
std::string s = debug_images_dir + "\\" + image_name + names[i] + ".png";
MinImg cropped_image = {0};
PROPAGATE_ERROR(GetMinImageRegion(&cropped_image, zone_image, rects[i].x, rects[i].y, rects[i].width, rects[i].height));
SaveMinImage(s.c_str(), &cropped_image);
}
return NO_ERRORS;
}
int DocumentZoneSegmentation(DocumentZoneSegmentationResult& cur_doc_zone_segmentation_result,
const DocumentImages& document_images,
const Result& final_result,
const EngineSettings& set,
std::string image_name) {
SegmentationResult serial_segmentation;
PROPAGATE_ERROR(Segmentation(serial_segmentation, &document_images.serial_, set, image_name));
cur_doc_zone_segmentation_result.serial_ = serial_segmentation;
if (final_result.GetType() != "TAG_Y") {
SegmentationResult row_segmentation;
SegmentationResult place_segmentation;
PROPAGATE_ERROR(Segmentation(row_segmentation, &document_images.row_, set, image_name));
PROPAGATE_ERROR(Segmentation(place_segmentation, &document_images.place_, set, image_name));
cur_doc_zone_segmentation_result.row_ = row_segmentation;
cur_doc_zone_segmentation_result.place_ = place_segmentation;
}
return NO_ERRORS;
}
}